From 9ba4cb69331386dfde9bac69dc2d5b22401face3 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 12 Dec 2018 16:56:02 -0800 Subject: collapse boilerplate folder structure within src/ folders (#505) * collapse boilerplate folder structure within src/ folders * . --- scalalib/api/src/ZincWorkerApi.scala | 76 ++ .../api/src/mill/scalalib/api/ZincWorkerApi.scala | 76 -- .../backgroundwrapper/src/BackgroundWrapper.java | 40 + .../backgroundwrapper/BackgroundWrapper.java | 40 - scalalib/src/Dep.scala | 121 +++ scalalib/src/Dependency.scala | 22 + scalalib/src/GenIdeaImpl.scala | 474 +++++++++++ scalalib/src/JavaModule.scala | 608 ++++++++++++++ scalalib/src/Lib.scala | 133 +++ scalalib/src/MiscModule.scala | 101 +++ scalalib/src/PublishModule.scala | 124 +++ scalalib/src/ScalaModule.scala | 275 ++++++ scalalib/src/TestRunner.scala | 153 ++++ scalalib/src/Versions.scala | 8 + scalalib/src/ZincWorkerModule.scala | 56 ++ .../src/dependency/DependencyUpdatesImpl.scala | 52 ++ .../dependency/metadata/MavenMetadataLoader.scala | 21 + .../src/dependency/metadata/MetadataLoader.scala | 7 + .../metadata/MetadataLoaderFactory.scala | 11 + .../updates/ModuleDependenciesUpdates.scala | 15 + .../src/dependency/updates/UpdatesFinder.scala | 75 ++ .../versions/ModuleDependenciesVersions.scala | 12 + scalalib/src/dependency/versions/Version.scala | 227 +++++ .../src/dependency/versions/VersionParser.scala | 30 + .../src/dependency/versions/VersionsFinder.scala | 73 ++ scalalib/src/mill/scalalib/Dep.scala | 121 --- scalalib/src/mill/scalalib/Dependency.scala | 22 - scalalib/src/mill/scalalib/GenIdeaImpl.scala | 474 ----------- scalalib/src/mill/scalalib/JavaModule.scala | 608 -------------- scalalib/src/mill/scalalib/Lib.scala | 133 --- scalalib/src/mill/scalalib/MiscModule.scala | 101 --- scalalib/src/mill/scalalib/PublishModule.scala | 124 --- scalalib/src/mill/scalalib/ScalaModule.scala | 275 ------ scalalib/src/mill/scalalib/TestRunner.scala | 153 ---- scalalib/src/mill/scalalib/Versions.scala | 8 - scalalib/src/mill/scalalib/ZincWorkerModule.scala | 56 -- .../dependency/DependencyUpdatesImpl.scala | 52 -- .../dependency/metadata/MavenMetadataLoader.scala | 21 - .../dependency/metadata/MetadataLoader.scala | 7 - .../metadata/MetadataLoaderFactory.scala | 11 - .../updates/ModuleDependenciesUpdates.scala | 15 - .../dependency/updates/UpdatesFinder.scala | 75 -- .../versions/ModuleDependenciesVersions.scala | 12 - .../scalalib/dependency/versions/Version.scala | 227 ----- .../dependency/versions/VersionParser.scala | 30 - .../dependency/versions/VersionsFinder.scala | 73 -- scalalib/src/mill/scalalib/package.scala | 12 - scalalib/src/mill/scalalib/publish/Ivy.scala | 59 -- .../src/mill/scalalib/publish/JsonFormatters.scala | 11 - scalalib/src/mill/scalalib/publish/Licence.scala | 479 ----------- .../src/mill/scalalib/publish/LocalPublisher.scala | 32 - scalalib/src/mill/scalalib/publish/Pom.scala | 117 --- .../mill/scalalib/publish/SonatypeHttpApi.scala | 134 --- .../mill/scalalib/publish/SonatypePublisher.scala | 164 ---- .../src/mill/scalalib/publish/VersionControl.scala | 131 --- scalalib/src/mill/scalalib/publish/package.scala | 3 - scalalib/src/mill/scalalib/publish/settings.scala | 91 -- .../mill/scalalib/scalafmt/ScalafmtModule.scala | 57 -- .../mill/scalalib/scalafmt/ScalafmtWorker.scala | 57 -- scalalib/src/package.scala | 12 + scalalib/src/publish/Ivy.scala | 59 ++ scalalib/src/publish/JsonFormatters.scala | 11 + scalalib/src/publish/Licence.scala | 479 +++++++++++ scalalib/src/publish/LocalPublisher.scala | 32 + scalalib/src/publish/Pom.scala | 117 +++ scalalib/src/publish/SonatypeHttpApi.scala | 134 +++ scalalib/src/publish/SonatypePublisher.scala | 164 ++++ scalalib/src/publish/VersionControl.scala | 131 +++ scalalib/src/publish/package.scala | 3 + scalalib/src/publish/settings.scala | 91 ++ scalalib/src/scalafmt/ScalafmtModule.scala | 57 ++ scalalib/src/scalafmt/ScalafmtWorker.scala | 57 ++ .../test/resources/hello-java/app/src/Main.java | 10 + .../resources/hello-java/app/src/hello/Main.java | 10 - .../hello-java/app/test/src/MyAppTests.java | 18 + .../hello-java/app/test/src/hello/MyAppTests.java | 18 - .../test/resources/hello-java/core/src/Core.java | 7 + .../resources/hello-java/core/src/hello/Core.java | 7 - .../hello-java/core/test/src/MyCoreTests.java | 15 + .../core/test/src/hello/MyCoreTests.java | 15 - scalalib/test/src/GenIdeaTests.scala | 62 ++ scalalib/test/src/HelloJavaTests.scala | 114 +++ scalalib/test/src/HelloWorldTests.scala | 934 +++++++++++++++++++++ scalalib/test/src/ResolveDepsTests.scala | 77 ++ scalalib/test/src/VersionControlTests.scala | 74 ++ .../metadata/MetadataLoaderFactoryTests.scala | 64 ++ .../dependency/updates/UpdatesFinderTests.scala | 173 ++++ .../src/dependency/versions/VersionTests.scala | 138 +++ scalalib/test/src/mill/scalalib/GenIdeaTests.scala | 62 -- .../test/src/mill/scalalib/HelloJavaTests.scala | 114 --- .../test/src/mill/scalalib/HelloWorldTests.scala | 934 --------------------- .../test/src/mill/scalalib/ResolveDepsTests.scala | 77 -- .../src/mill/scalalib/VersionControlTests.scala | 74 -- .../metadata/MetadataLoaderFactoryTests.scala | 64 -- .../dependency/updates/UpdatesFinderTests.scala | 173 ---- .../dependency/versions/VersionTests.scala | 138 --- .../test/src/mill/scalalib/publish/IvyTests.scala | 60 -- .../test/src/mill/scalalib/publish/PomTests.scala | 205 ----- .../src/mill/scalalib/scalafmt/ScalafmtTests.scala | 104 --- scalalib/test/src/publish/IvyTests.scala | 60 ++ scalalib/test/src/publish/PomTests.scala | 205 +++++ scalalib/test/src/scalafmt/ScalafmtTests.scala | 104 +++ scalalib/worker/src/ZincWorkerImpl.scala | 284 +++++++ .../src/mill/scalalib/worker/ZincWorkerImpl.scala | 284 ------- 104 files changed, 6400 insertions(+), 6400 deletions(-) create mode 100644 scalalib/api/src/ZincWorkerApi.scala delete mode 100644 scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala create mode 100644 scalalib/backgroundwrapper/src/BackgroundWrapper.java delete mode 100644 scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java create mode 100644 scalalib/src/Dep.scala create mode 100644 scalalib/src/Dependency.scala create mode 100644 scalalib/src/GenIdeaImpl.scala create mode 100644 scalalib/src/JavaModule.scala create mode 100644 scalalib/src/Lib.scala create mode 100644 scalalib/src/MiscModule.scala create mode 100644 scalalib/src/PublishModule.scala create mode 100644 scalalib/src/ScalaModule.scala create mode 100644 scalalib/src/TestRunner.scala create mode 100644 scalalib/src/Versions.scala create mode 100644 scalalib/src/ZincWorkerModule.scala create mode 100644 scalalib/src/dependency/DependencyUpdatesImpl.scala create mode 100644 scalalib/src/dependency/metadata/MavenMetadataLoader.scala create mode 100644 scalalib/src/dependency/metadata/MetadataLoader.scala create mode 100644 scalalib/src/dependency/metadata/MetadataLoaderFactory.scala create mode 100644 scalalib/src/dependency/updates/ModuleDependenciesUpdates.scala create mode 100644 scalalib/src/dependency/updates/UpdatesFinder.scala create mode 100644 scalalib/src/dependency/versions/ModuleDependenciesVersions.scala create mode 100644 scalalib/src/dependency/versions/Version.scala create mode 100644 scalalib/src/dependency/versions/VersionParser.scala create mode 100644 scalalib/src/dependency/versions/VersionsFinder.scala delete mode 100644 scalalib/src/mill/scalalib/Dep.scala delete mode 100644 scalalib/src/mill/scalalib/Dependency.scala delete mode 100644 scalalib/src/mill/scalalib/GenIdeaImpl.scala delete mode 100644 scalalib/src/mill/scalalib/JavaModule.scala delete mode 100644 scalalib/src/mill/scalalib/Lib.scala delete mode 100644 scalalib/src/mill/scalalib/MiscModule.scala delete mode 100644 scalalib/src/mill/scalalib/PublishModule.scala delete mode 100644 scalalib/src/mill/scalalib/ScalaModule.scala delete mode 100644 scalalib/src/mill/scalalib/TestRunner.scala delete mode 100644 scalalib/src/mill/scalalib/Versions.scala delete mode 100644 scalalib/src/mill/scalalib/ZincWorkerModule.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/DependencyUpdatesImpl.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/metadata/MavenMetadataLoader.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/metadata/MetadataLoader.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/metadata/MetadataLoaderFactory.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/updates/ModuleDependenciesUpdates.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/updates/UpdatesFinder.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/versions/ModuleDependenciesVersions.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/versions/Version.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/versions/VersionParser.scala delete mode 100644 scalalib/src/mill/scalalib/dependency/versions/VersionsFinder.scala delete mode 100644 scalalib/src/mill/scalalib/package.scala delete mode 100644 scalalib/src/mill/scalalib/publish/Ivy.scala delete mode 100644 scalalib/src/mill/scalalib/publish/JsonFormatters.scala delete mode 100644 scalalib/src/mill/scalalib/publish/Licence.scala delete mode 100644 scalalib/src/mill/scalalib/publish/LocalPublisher.scala delete mode 100644 scalalib/src/mill/scalalib/publish/Pom.scala delete mode 100644 scalalib/src/mill/scalalib/publish/SonatypeHttpApi.scala delete mode 100644 scalalib/src/mill/scalalib/publish/SonatypePublisher.scala delete mode 100644 scalalib/src/mill/scalalib/publish/VersionControl.scala delete mode 100644 scalalib/src/mill/scalalib/publish/package.scala delete mode 100644 scalalib/src/mill/scalalib/publish/settings.scala delete mode 100644 scalalib/src/mill/scalalib/scalafmt/ScalafmtModule.scala delete mode 100644 scalalib/src/mill/scalalib/scalafmt/ScalafmtWorker.scala create mode 100644 scalalib/src/package.scala create mode 100644 scalalib/src/publish/Ivy.scala create mode 100644 scalalib/src/publish/JsonFormatters.scala create mode 100644 scalalib/src/publish/Licence.scala create mode 100644 scalalib/src/publish/LocalPublisher.scala create mode 100644 scalalib/src/publish/Pom.scala create mode 100644 scalalib/src/publish/SonatypeHttpApi.scala create mode 100644 scalalib/src/publish/SonatypePublisher.scala create mode 100644 scalalib/src/publish/VersionControl.scala create mode 100644 scalalib/src/publish/package.scala create mode 100644 scalalib/src/publish/settings.scala create mode 100644 scalalib/src/scalafmt/ScalafmtModule.scala create mode 100644 scalalib/src/scalafmt/ScalafmtWorker.scala create mode 100644 scalalib/test/resources/hello-java/app/src/Main.java delete mode 100644 scalalib/test/resources/hello-java/app/src/hello/Main.java create mode 100644 scalalib/test/resources/hello-java/app/test/src/MyAppTests.java delete mode 100644 scalalib/test/resources/hello-java/app/test/src/hello/MyAppTests.java create mode 100644 scalalib/test/resources/hello-java/core/src/Core.java delete mode 100644 scalalib/test/resources/hello-java/core/src/hello/Core.java create mode 100644 scalalib/test/resources/hello-java/core/test/src/MyCoreTests.java delete mode 100644 scalalib/test/resources/hello-java/core/test/src/hello/MyCoreTests.java create mode 100644 scalalib/test/src/GenIdeaTests.scala create mode 100644 scalalib/test/src/HelloJavaTests.scala create mode 100644 scalalib/test/src/HelloWorldTests.scala create mode 100644 scalalib/test/src/ResolveDepsTests.scala create mode 100644 scalalib/test/src/VersionControlTests.scala create mode 100644 scalalib/test/src/dependency/metadata/MetadataLoaderFactoryTests.scala create mode 100644 scalalib/test/src/dependency/updates/UpdatesFinderTests.scala create mode 100644 scalalib/test/src/dependency/versions/VersionTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/GenIdeaTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/HelloJavaTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/HelloWorldTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/ResolveDepsTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/VersionControlTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/dependency/metadata/MetadataLoaderFactoryTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/dependency/updates/UpdatesFinderTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/dependency/versions/VersionTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/publish/IvyTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/publish/PomTests.scala delete mode 100644 scalalib/test/src/mill/scalalib/scalafmt/ScalafmtTests.scala create mode 100644 scalalib/test/src/publish/IvyTests.scala create mode 100644 scalalib/test/src/publish/PomTests.scala create mode 100644 scalalib/test/src/scalafmt/ScalafmtTests.scala create mode 100644 scalalib/worker/src/ZincWorkerImpl.scala delete mode 100644 scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala (limited to 'scalalib') diff --git a/scalalib/api/src/ZincWorkerApi.scala b/scalalib/api/src/ZincWorkerApi.scala new file mode 100644 index 00000000..c5230ec5 --- /dev/null +++ b/scalalib/api/src/ZincWorkerApi.scala @@ -0,0 +1,76 @@ +package mill.scalalib.api + +import mill.api.Loose.Agg +import mill.api.PathRef +import mill.api.JsonFormatters._ + +trait ZincWorkerApi { + /** Compile a Java-only project */ + def compileJava(upstreamCompileOutput: Seq[CompilationResult], + sources: Agg[os.Path], + compileClasspath: Agg[os.Path], + javacOptions: Seq[String]) + (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] + + /** Compile a mixed Scala/Java or Scala-only project */ + def compileMixed(upstreamCompileOutput: Seq[CompilationResult], + sources: Agg[os.Path], + compileClasspath: Agg[os.Path], + javacOptions: Seq[String], + scalaVersion: String, + scalacOptions: Seq[String], + compilerBridgeSources: os.Path, + compilerClasspath: Agg[os.Path], + scalacPluginClasspath: Agg[os.Path]) + (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] + + def discoverMainClasses(compilationResult: CompilationResult) + (implicit ctx: mill.api.Ctx): Seq[String] + + def docJar(scalaVersion: String, + compilerBridgeSources: os.Path, + compilerClasspath: Agg[os.Path], + scalacPluginClasspath: Agg[os.Path], + args: Seq[String]) + (implicit ctx: mill.api.Ctx): Boolean +} + + +object CompilationResult { + implicit val jsonFormatter: upickle.default.ReadWriter[CompilationResult] = upickle.default.macroRW +} + +// analysisFile is represented by os.Path, so we won't break caches after file changes +case class CompilationResult(analysisFile: os.Path, classes: PathRef) + +object Util{ + def isDotty(scalaVersion: String) = + scalaVersion.startsWith("0.") + + + def grepJar(classPath: Agg[os.Path], name: String, version: String, sources: Boolean = false) = { + val suffix = if (sources) "-sources" else "" + val mavenStylePath = s"$name-$version$suffix.jar" + val ivyStylePath = { + val dir = if (sources) "srcs" else "jars" + s"$version/$dir/$name$suffix.jar" + } + + classPath + .find(p => p.toString.endsWith(mavenStylePath) || p.toString.endsWith(ivyStylePath)) + .getOrElse(throw new Exception(s"Cannot find $mavenStylePath or $ivyStylePath")) + } + + private val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r + private val MinorSnapshotVersion = raw"""(\d+)\.(\d+)\.([1-9]\d*)-SNAPSHOT""".r + private val DottyVersion = raw"""0\.(\d+)\.(\d+).*""".r + + def scalaBinaryVersion(scalaVersion: String) = { + scalaVersion match { + case ReleaseVersion(major, minor, _) => s"$major.$minor" + case MinorSnapshotVersion(major, minor, _) => s"$major.$minor" + case DottyVersion(minor, _) => s"0.$minor" + case _ => scalaVersion + } + } +} \ No newline at end of file diff --git a/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala b/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala deleted file mode 100644 index c5230ec5..00000000 --- a/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala +++ /dev/null @@ -1,76 +0,0 @@ -package mill.scalalib.api - -import mill.api.Loose.Agg -import mill.api.PathRef -import mill.api.JsonFormatters._ - -trait ZincWorkerApi { - /** Compile a Java-only project */ - def compileJava(upstreamCompileOutput: Seq[CompilationResult], - sources: Agg[os.Path], - compileClasspath: Agg[os.Path], - javacOptions: Seq[String]) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] - - /** Compile a mixed Scala/Java or Scala-only project */ - def compileMixed(upstreamCompileOutput: Seq[CompilationResult], - sources: Agg[os.Path], - compileClasspath: Agg[os.Path], - javacOptions: Seq[String], - scalaVersion: String, - scalacOptions: Seq[String], - compilerBridgeSources: os.Path, - compilerClasspath: Agg[os.Path], - scalacPluginClasspath: Agg[os.Path]) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] - - def discoverMainClasses(compilationResult: CompilationResult) - (implicit ctx: mill.api.Ctx): Seq[String] - - def docJar(scalaVersion: String, - compilerBridgeSources: os.Path, - compilerClasspath: Agg[os.Path], - scalacPluginClasspath: Agg[os.Path], - args: Seq[String]) - (implicit ctx: mill.api.Ctx): Boolean -} - - -object CompilationResult { - implicit val jsonFormatter: upickle.default.ReadWriter[CompilationResult] = upickle.default.macroRW -} - -// analysisFile is represented by os.Path, so we won't break caches after file changes -case class CompilationResult(analysisFile: os.Path, classes: PathRef) - -object Util{ - def isDotty(scalaVersion: String) = - scalaVersion.startsWith("0.") - - - def grepJar(classPath: Agg[os.Path], name: String, version: String, sources: Boolean = false) = { - val suffix = if (sources) "-sources" else "" - val mavenStylePath = s"$name-$version$suffix.jar" - val ivyStylePath = { - val dir = if (sources) "srcs" else "jars" - s"$version/$dir/$name$suffix.jar" - } - - classPath - .find(p => p.toString.endsWith(mavenStylePath) || p.toString.endsWith(ivyStylePath)) - .getOrElse(throw new Exception(s"Cannot find $mavenStylePath or $ivyStylePath")) - } - - private val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r - private val MinorSnapshotVersion = raw"""(\d+)\.(\d+)\.([1-9]\d*)-SNAPSHOT""".r - private val DottyVersion = raw"""0\.(\d+)\.(\d+).*""".r - - def scalaBinaryVersion(scalaVersion: String) = { - scalaVersion match { - case ReleaseVersion(major, minor, _) => s"$major.$minor" - case MinorSnapshotVersion(major, minor, _) => s"$major.$minor" - case DottyVersion(minor, _) => s"0.$minor" - case _ => scalaVersion - } - } -} \ No newline at end of file diff --git a/scalalib/backgroundwrapper/src/BackgroundWrapper.java b/scalalib/backgroundwrapper/src/BackgroundWrapper.java new file mode 100644 index 00000000..02ee23eb --- /dev/null +++ b/scalalib/backgroundwrapper/src/BackgroundWrapper.java @@ -0,0 +1,40 @@ +package mill.scalalib.backgroundwrapper; + +public class BackgroundWrapper { + public static void main(String[] args) throws Exception{ + String watched = args[0]; + String tombstone = args[1]; + String expected = args[2]; + Thread watcher = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try{ + Thread.sleep(50); + String token = new String( + java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(watched)) + ); + if (!token.equals(expected)) { + new java.io.File(tombstone).createNewFile(); + System.exit(0); + } + }catch(Exception e){ + try { + new java.io.File(tombstone).createNewFile(); + }catch(Exception e2){} + System.exit(0); + } + + } + } + }); + watcher.setDaemon(true); + watcher.start(); + String realMain = args[3]; + String[] realArgs = new String[args.length - 4]; + for(int i = 0; i < args.length-4; i++){ + realArgs[i] = args[i+4]; + } + Class.forName(realMain).getMethod("main", String[].class).invoke(null, (Object)realArgs); + } +} diff --git a/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java b/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java deleted file mode 100644 index 02ee23eb..00000000 --- a/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java +++ /dev/null @@ -1,40 +0,0 @@ -package mill.scalalib.backgroundwrapper; - -public class BackgroundWrapper { - public static void main(String[] args) throws Exception{ - String watched = args[0]; - String tombstone = args[1]; - String expected = args[2]; - Thread watcher = new Thread(new Runnable() { - @Override - public void run() { - while (true) { - try{ - Thread.sleep(50); - String token = new String( - java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(watched)) - ); - if (!token.equals(expected)) { - new java.io.File(tombstone).createNewFile(); - System.exit(0); - } - }catch(Exception e){ - try { - new java.io.File(tombstone).createNewFile(); - }catch(Exception e2){} - System.exit(0); - } - - } - } - }); - watcher.setDaemon(true); - watcher.start(); - String realMain = args[3]; - String[] realArgs = new String[args.length - 4]; - for(int i = 0; i < args.length-4; i++){ - realArgs[i] = args[i+4]; - } - Class.forName(realMain).getMethod("main", String[].class).invoke(null, (Object)realArgs); - } -} diff --git a/scalalib/src/Dep.scala b/scalalib/src/Dep.scala new file mode 100644 index 00000000..714fa21e --- /dev/null +++ b/scalalib/src/Dep.scala @@ -0,0 +1,121 @@ +package mill.scalalib +import mill.util.JsonFormatters._ +import upickle.default.{macroRW, ReadWriter => RW} + +import CrossVersion._ + +case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) { + import mill.scalalib.api.Util.isDotty + + def artifactName(binaryVersion: String, fullVersion: String, platformSuffix: String) = { + val suffix = cross.suffixString(binaryVersion, fullVersion, platformSuffix) + dep.module.name + suffix + } + def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes)) + def forceVersion(): Dep = copy(force = true) + def exclude(exclusions: (String, String)*) = copy(dep = dep.copy(exclusions = dep.exclusions ++ exclusions)) + def excludeOrg(organizations: String*): Dep = exclude(organizations.map(_ -> "*"): _*) + def excludeName(names: String*): Dep = exclude(names.map("*" -> _): _*) + def toDependency(binaryVersion: String, fullVersion: String, platformSuffix: String) = + dep.copy(module = dep.module.copy(name = artifactName(binaryVersion, fullVersion, platformSuffix))) + def withConfiguration(configuration: String): Dep = copy(dep = dep.copy(configuration = configuration)) + + /** + * If scalaVersion is a Dotty version, replace the cross-version suffix + * by the Scala 2.x version that the Dotty version is retro-compatible with, + * otherwise do nothing. + * + * This setting is useful when your build contains dependencies that have only + * been published with Scala 2.x, if you have: + * {{{ + * def ivyDeps = Agg(ivy"a::b:c") + * }}} + * you can replace it by: + * {{{ + * def ivyDeps = Agg(ivy"a::b:c".withDottyCompat(scalaVersion())) + * }}} + * This will have no effect when compiling with Scala 2.x, but when compiling + * with Dotty this will change the cross-version to a Scala 2.x one. This + * works because Dotty is currently retro-compatible with Scala 2.x. + */ + def withDottyCompat(scalaVersion: String): Dep = + cross match { + case cross: Binary if isDotty(scalaVersion) => + copy(cross = Constant(value = "_2.12", platformed = cross.platformed)) + case _ => + this + } +} + +object Dep { + + val DefaultConfiguration = "default(compile)" + + implicit def parse(signature: String): Dep = { + val parts = signature.split(';') + val module = parts.head + val attributes = parts.tail.foldLeft(coursier.Attributes()) { (as, s) => + s.split('=') match { + case Array("classifier", v) => as.copy(classifier = v) + case Array(k, v) => throw new Exception(s"Unrecognized attribute: [$s]") + case _ => throw new Exception(s"Unable to parse attribute specifier: [$s]") + } + } + (module.split(':') match { + case Array(a, b, c) => Dep(a, b, c, cross = empty(platformed = false)) + case Array(a, b, "", c) => Dep(a, b, c, cross = empty(platformed = true)) + case Array(a, "", b, c) => Dep(a, b, c, cross = Binary(platformed = false)) + case Array(a, "", b, "", c) => Dep(a, b, c, cross = Binary(platformed = true)) + case Array(a, "", "", b, c) => Dep(a, b, c, cross = Full(platformed = false)) + case Array(a, "", "", b, "", c) => Dep(a, b, c, cross = Full(platformed = true)) + case _ => throw new Exception(s"Unable to parse signature: [$signature]") + }).configure(attributes = attributes) + } + def apply(org: String, name: String, version: String, cross: CrossVersion, force: Boolean = false): Dep = { + apply(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force) + } + implicit def rw: RW[Dep] = macroRW +} + +sealed trait CrossVersion { + /** If true, the cross-version suffix should start with a platform suffix if it exists */ + def platformed: Boolean + + def isBinary: Boolean = + this.isInstanceOf[Binary] + def isConstant: Boolean = + this.isInstanceOf[Constant] + def isFull: Boolean = + this.isInstanceOf[Full] + + /** The string that should be appended to the module name to get the artifact name */ + def suffixString(binaryVersion: String, fullVersion: String, platformSuffix: String): String = { + val firstSuffix = if (platformed) platformSuffix else "" + this match { + case cross: Constant => + s"${firstSuffix}${cross.value}" + case cross: Binary => + s"${firstSuffix}_${binaryVersion}" + case cross: Full => + s"${firstSuffix}_${fullVersion}" + } + } +} +object CrossVersion { + case class Constant(value: String, platformed: Boolean) extends CrossVersion + object Constant { + implicit def rw: RW[Constant] = macroRW + } + case class Binary(platformed: Boolean) extends CrossVersion + object Binary { + implicit def rw: RW[Binary] = macroRW + } + case class Full(platformed: Boolean) extends CrossVersion + object Full { + implicit def rw: RW[Full] = macroRW + } + + def empty(platformed: Boolean) = Constant(value = "", platformed) + + implicit def rw: RW[CrossVersion] = RW.merge(Constant.rw, Binary.rw, Full.rw) +} diff --git a/scalalib/src/Dependency.scala b/scalalib/src/Dependency.scala new file mode 100644 index 00000000..0c589663 --- /dev/null +++ b/scalalib/src/Dependency.scala @@ -0,0 +1,22 @@ +package mill.scalalib + +import mill.T +import mill.define.{Discover, ExternalModule} +import mill.eval.Evaluator +import mill.main.EvaluatorScopt +import mill.scalalib.dependency.DependencyUpdatesImpl + +object Dependency extends ExternalModule { + + def updates(ev: Evaluator, allowPreRelease: Boolean = false) = + T.command { + DependencyUpdatesImpl(implicitly, + ev.rootModule, + ev.rootModule.millDiscover, + allowPreRelease) + } + + implicit def millScoptEvaluatorReads[T]: EvaluatorScopt[T] = + new mill.main.EvaluatorScopt[T]() + lazy val millDiscover: Discover[Dependency.this.type] = Discover[this.type] +} diff --git a/scalalib/src/GenIdeaImpl.scala b/scalalib/src/GenIdeaImpl.scala new file mode 100644 index 00000000..2d76d804 --- /dev/null +++ b/scalalib/src/GenIdeaImpl.scala @@ -0,0 +1,474 @@ +package mill.scalalib + +import ammonite.runtime.SpecialClassLoader +import coursier.{Cache, CoursierPaths, Repository} +import mill.define._ +import mill.eval.{Evaluator, PathRef, Result} +import mill.api.Ctx.{Home, Log} +import mill.util.Strict.Agg +import mill.util.{Loose, Strict} +import mill.{T, scalalib} + +import scala.util.Try + + +object GenIdea extends ExternalModule { + + def idea(ev: Evaluator) = T.command{ + mill.scalalib.GenIdeaImpl( + implicitly, + ev.rootModule, + ev.rootModule.millDiscover + ) + } + + implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]() + lazy val millDiscover = Discover[this.type] +} + +object GenIdeaImpl { + + def apply(ctx: Log with Home, + rootModule: BaseModule, + discover: Discover[_]): Unit = { + val pp = new scala.xml.PrettyPrinter(999, 4) + + val jdkInfo = extractCurrentJdk(os.pwd / ".idea" / "misc.xml").getOrElse(("JDK_1_8", "1.8 (1)")) + + os.remove.all(os.pwd/".idea"/"libraries") + os.remove.all(os.pwd/".idea"/"scala_compiler.xml") + os.remove.all(os.pwd/".idea_modules") + + + val evaluator = new Evaluator(ctx.home, os.pwd / 'out, os.pwd / 'out, rootModule, ctx.log) + + for((relPath, xml) <- xmlFileLayout(evaluator, rootModule, jdkInfo)){ + os.write.over(os.pwd/relPath, pp.format(xml)) + } + } + + def extractCurrentJdk(ideaPath: os.Path): Option[(String,String)] = { + import scala.xml.XML + Try { + val xml = XML.loadFile(ideaPath.toString) + (xml \\ "component") + .filter(x => x.attribute("project-jdk-type").map(_.text).contains("JavaSDK")) + .map { n => (n.attribute("languageLevel"), n.attribute("project-jdk-name")) } + .collectFirst{ case (Some(lang), Some(jdk)) => (lang.text, jdk.text) } + }.getOrElse(None) + } + + def xmlFileLayout(evaluator: Evaluator, + rootModule: mill.Module, + jdkInfo: (String,String), + fetchMillModules: Boolean = true): Seq[(os.RelPath, scala.xml.Node)] = { + + val modules = rootModule.millInternal.segmentsToModules.values + .collect{ case x: scalalib.JavaModule => (x.millModuleSegments, x)} + .toSeq + + val buildLibraryPaths = + if (!fetchMillModules) Nil + else sys.props.get("MILL_BUILD_LIBRARIES") match { + case Some(found) => found.split(',').map(os.Path(_)).distinct.toList + case None => + val repos = modules.foldLeft(Set.empty[Repository]) { _ ++ _._2.repositories } + val artifactNames = Seq("main-moduledefs", "main-core", "scalalib", "scalajslib") + val Result.Success(res) = scalalib.Lib.resolveDependencies( + repos.toList, + Lib.depToDependency(_, "2.12.4", ""), + for(name <- artifactNames) + yield ivy"com.lihaoyi::mill-$name:${sys.props("MILL_VERSION")}" + ) + res.items.toList.map(_.path) + } + + val buildDepsPaths = Try(evaluator + .rootModule + .getClass + .getClassLoader + .asInstanceOf[SpecialClassLoader] + ).map { + _.allJars + .map(url => os.Path(url.getFile)) + .filter(_.toIO.exists) + }.getOrElse(Seq()) + + val resolved = for((path, mod) <- modules) yield { + val scalaLibraryIvyDeps = mod match{ + case x: ScalaModule => x.scalaLibraryIvyDeps + case _ => T.task{Nil} + } + val allIvyDeps = T.task{mod.transitiveIvyDeps() ++ scalaLibraryIvyDeps() ++ mod.compileIvyDeps()} + val externalDependencies = T.task{ + mod.resolveDeps(allIvyDeps)() ++ + Task.traverse(mod.transitiveModuleDeps)(_.unmanagedClasspath)().flatten + } + + val externalSources = T.task{ + mod.resolveDeps(allIvyDeps, sources = true)() + } + + val (scalacPluginsIvyDeps, scalacOptions) = mod match{ + case mod: ScalaModule => T.task{mod.scalacPluginIvyDeps()} -> T.task{mod.scalacOptions()} + case _ => T.task(Loose.Agg[Dep]()) -> T.task(Seq()) + } + val scalacPluginDependencies = T.task{ + mod.resolveDeps(scalacPluginsIvyDeps)() + } + + val resolvedCp: Loose.Agg[PathRef] = evalOrElse(evaluator, externalDependencies, Loose.Agg.empty) + val resolvedSrcs: Loose.Agg[PathRef] = evalOrElse(evaluator, externalSources, Loose.Agg.empty) + val resolvedSp: Loose.Agg[PathRef] = evalOrElse(evaluator, scalacPluginDependencies, Loose.Agg.empty) + val scalacOpts: Seq[String] = evalOrElse(evaluator, scalacOptions, Seq()) + + ( + path, + resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path), + mod, + resolvedSp.map(_.path).filter(_.ext == "jar"), + scalacOpts + ) + } + val moduleLabels = modules.map(_.swap).toMap + + val allResolved = resolved.flatMap(_._2) ++ buildLibraryPaths ++ buildDepsPaths + + val commonPrefix = + if (allResolved.isEmpty) 0 + else { + val minResolvedLength = allResolved.map(_.segmentCount).min + allResolved.map(_.segments.take(minResolvedLength).toList) + .transpose + .takeWhile(_.distinct.length == 1) + .length + } + + // only resort to full long path names if the jar name is a duplicate + val pathShortLibNameDuplicate = allResolved + .distinct + .map{p => p.last -> p} + .groupBy(_._1) + .filter(_._2.size > 1) + .keySet + + val pathToLibName = allResolved + .map{p => + if (pathShortLibNameDuplicate(p.last)) + (p, p.segments.drop(commonPrefix).mkString("_")) + else + (p, p.last) + } + .toMap + + sealed trait ResolvedLibrary { def path : os.Path } + case class CoursierResolved(path : os.Path, pom : os.Path, sources : Option[os.Path]) + extends ResolvedLibrary + case class OtherResolved(path : os.Path) extends ResolvedLibrary + + // Tries to group jars with their poms and sources. + def toResolvedJar(path : os.Path) : Option[ResolvedLibrary] = { + val inCoursierCache = path.startsWith(os.Path(CoursierPaths.cacheDirectory())) + val isSource = path.last.endsWith("sources.jar") + val isPom = path.ext == "pom" + if (inCoursierCache && (isSource || isPom)) { + // Remove sources and pom as they'll be recovered from the jar path + None + } else if (inCoursierCache && path.ext == "jar") { + val withoutExt = path.last.dropRight(path.ext.length + 1) + val pom = path / os.up / s"$withoutExt.pom" + val sources = Some(path / os.up / s"$withoutExt-sources.jar") + .filter(_.toIO.exists()) + Some(CoursierResolved(path, pom, sources)) + } else Some(OtherResolved(path)) + } + + // Hack so that Intellij does not complain about unresolved magic + // imports in build.sc when in fact they are resolved + def sbtLibraryNameFromPom(pom : os.Path) : String = { + val xml = scala.xml.XML.loadFile(pom.toIO) + + val groupId = (xml \ "groupId").text + val artifactId = (xml \ "artifactId").text + val version = (xml \ "version").text + + // The scala version here is non incidental + s"SBT: $groupId:$artifactId:$version:jar" + } + + def libraryName(resolvedJar: ResolvedLibrary) : String = resolvedJar match { + case CoursierResolved(path, pom, _) if buildDepsPaths.contains(path) => + sbtLibraryNameFromPom(pom) + case CoursierResolved(path, _, _) => + pathToLibName(path) + case OtherResolved(path) => + pathToLibName(path) + } + + def resolvedLibraries(resolved : Seq[os.Path]) : Seq[ResolvedLibrary] = resolved + .map(toResolvedJar) + .collect { case Some(r) => r} + + val compilerSettings = resolved + .foldLeft(Map[(Loose.Agg[os.Path], Seq[String]), Vector[JavaModule]]()) { + (r, q) => + val key = (q._4, q._5) + r + (key -> (r.getOrElse(key, Vector()) :+ q._3)) + } + + val allBuildLibraries : Set[ResolvedLibrary] = + resolvedLibraries(buildLibraryPaths ++ buildDepsPaths).toSet + + val fixedFiles = Seq( + Tuple2(os.rel/".idea"/"misc.xml", miscXmlTemplate(jdkInfo)), + Tuple2(os.rel/".idea"/"scala_settings.xml", scalaSettingsTemplate()), + Tuple2( + os.rel/".idea"/"modules.xml", + allModulesXmlTemplate( + modules + .filter(!_._2.skipIdea) + .map { case (path, mod) => moduleName(path) } + ) + ), + Tuple2( + os.rel/".idea_modules"/"mill-build.iml", + rootXmlTemplate( + for(lib <- allBuildLibraries) + yield libraryName(lib) + ) + ), + Tuple2( + os.rel/".idea"/"scala_compiler.xml", + scalaCompilerTemplate(compilerSettings) + ) + ) + + val libraries = resolvedLibraries(allResolved).map{ resolved => + import resolved.path + val url = "jar://" + path + "!/" + val name = libraryName(resolved) + val sources = resolved match { + case CoursierResolved(_, _, s) => s.map(p => "jar://" + p + "!/") + case OtherResolved(_) => None + } + Tuple2(os.rel/".idea"/'libraries/s"$name.xml", libraryXmlTemplate(name, url, sources)) + } + + val moduleFiles = resolved.map{ case (path, resolvedDeps, mod, _, _) => + val Seq( + resourcesPathRefs: Seq[PathRef], + sourcesPathRef: Seq[PathRef], + generatedSourcePathRefs: Seq[PathRef], + allSourcesPathRefs: Seq[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.outPath, + mod.compile.ctx.segments + ) + val scalaVersionOpt = mod match { + case x: ScalaModule => Some(evaluator.evaluate(Agg(x.scalaVersion)).values.head.asInstanceOf[String]) + case _ => None + } + val generatedSourceOutPath = Evaluator.resolveDestPaths( + evaluator.outPath, + mod.generatedSources.ctx.segments + ) + + val isTest = mod.isInstanceOf[TestModule] + + val elem = moduleXmlTemplate( + mod.intellijModulePath, + scalaVersionOpt, + Strict.Agg.from(resourcesPathRefs.map(_.path)), + Strict.Agg.from(normalSourcePaths), + Strict.Agg.from(generatedSourcePaths), + paths.out, + generatedSourceOutPath.dest, + Strict.Agg.from(resolvedDeps.map(pathToLibName)), + Strict.Agg.from(mod.moduleDeps.map{ m => moduleName(moduleLabels(m))}.distinct), + isTest + ) + Tuple2(os.rel/".idea_modules"/s"${moduleName(path)}.iml", elem) + } + + fixedFiles ++ libraries ++ moduleFiles + } + + def evalOrElse[T](evaluator: Evaluator, e: Task[T], default: => T): T = { + evaluator.evaluate(Agg(e)).values match { + case Seq() => default + case Seq(e: T) => e + } + } + + def relify(p: os.Path) = { + val r = p.relativeTo(os.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 scalaSettingsTemplate() = { + + + + + + } + def miscXmlTemplate(jdkInfo: (String,String)) = { + + + + + + } + + def allModulesXmlTemplate(selectors: Seq[String]) = { + + + + + { + for(selector <- selectors) + yield { + val filepath = "$PROJECT_DIR$/.idea_modules/" + selector + ".iml" + val fileurl = "file://" + filepath + + } + } + + + + } + def rootXmlTemplate(libNames: Strict.Agg[String]) = { + + + + + + + + + + + { + for(name <- libNames.toSeq.sorted) + yield + } + + + } + def libraryXmlTemplate(name: String, url: String, sources: Option[String]) = { + + + + + + { if (sources.isDefined) { + + + + } + } + + + } + def moduleXmlTemplate(basePath: os.Path, + scalaVersionOpt: Option[String], + resourcePaths: Strict.Agg[os.Path], + normalSourcePaths: Strict.Agg[os.Path], + generatedSourcePaths: Strict.Agg[os.Path], + compileOutputPath: os.Path, + generatedSourceOutputPath: os.Path, + libNames: Strict.Agg[String], + depNames: Strict.Agg[String], + isTest: Boolean + ) = { + + + { + val outputUrl = "file://$MODULE_DIR$/" + relify(compileOutputPath) + "/dest/classes" + if (isTest) + + else + + } + + + + { + for (normalSourcePath <- normalSourcePaths.toSeq.sorted) + yield + + } + { + for (generatedSourcePath <- generatedSourcePaths.toSeq.sorted) + yield + + } + { + val resourceType = if (isTest) "java-test-resource" else "java-resource" + for (resourcePath <- resourcePaths.toSeq.sorted) + yield + + } + + + + + { + for(scalaVersion <- scalaVersionOpt.toSeq) + yield + } + + { + for(name <- libNames.toSeq.sorted) + yield + + } + { + for(depName <- depNames.toSeq.sorted) + yield + } + + + } + def scalaCompilerTemplate(settings: Map[(Loose.Agg[os.Path], Seq[String]), Seq[JavaModule]]) = { + + + + { + for((((plugins, params), mods), i) <- settings.toSeq.zip(1 to settings.size)) + yield + moduleName(m.millModuleSegments)).mkString(",")}> + + { + for(param <- params) + yield + } + + + { + for(plugin <- plugins.toSeq) + yield + } + + + } + + + } +} diff --git a/scalalib/src/JavaModule.scala b/scalalib/src/JavaModule.scala new file mode 100644 index 00000000..78be8893 --- /dev/null +++ b/scalalib/src/JavaModule.scala @@ -0,0 +1,608 @@ +package mill +package scalalib + +import coursier.Repository +import mill.define.Task +import mill.define.TaskModule +import mill.eval.{PathRef, Result} +import mill.modules.{Assembly, Jvm} +import mill.modules.Jvm.{createAssembly, createJar} +import Lib._ +import mill.scalalib.publish.{Artifact, Scope} +import mill.util.Loose.Agg + +/** + * Core configuration required to compile a single Scala compilation target + */ +trait JavaModule extends mill.Module with TaskModule { outer => + def zincWorker: ZincWorkerModule = mill.scalalib.ZincWorkerModule + + trait Tests extends TestModule{ + override def moduleDeps = Seq(outer) + override def repositories = outer.repositories + override def javacOptions = outer.javacOptions + override def zincWorker = outer.zincWorker + } + def defaultCommandName() = "run" + + def resolvePublishDependency: Task[Dep => publish.Dependency] = T.task{ + Artifact.fromDepJava(_: Dep) + } + def resolveCoursierDependency: Task[Dep => coursier.Dependency] = T.task{ + Lib.depToDependencyJava(_: Dep) + } + + /** + * Allows you to specify an explicit main class to use for the `run` command. + * If none is specified, the classpath is searched for an appropriate main + * class to use if one exists + */ + def mainClass: T[Option[String]] = None + + def finalMainClassOpt: T[Either[String, String]] = T{ + mainClass() match{ + case Some(m) => Right(m) + case None => + zincWorker.worker().discoverMainClasses(compile())match { + case Seq() => Left("No main class specified or found") + case Seq(main) => Right(main) + case mains => + Left( + s"Multiple main classes found (${mains.mkString(",")}) " + + "please explicitly specify which one to use by overriding mainClass" + ) + } + } + } + + def finalMainClass: T[String] = T{ + finalMainClassOpt() match { + case Right(main) => Result.Success(main) + case Left(msg) => Result.Failure(msg) + } + } + + /** + * Any ivy dependencies you want to add to this Module, in the format + * ivy"org::name:version" for Scala dependencies or ivy"org:name:version" + * for Java dependencies + */ + def ivyDeps = T{ Agg.empty[Dep] } + + /** + * Same as `ivyDeps`, but only present at compile time. Useful for e.g. + * macro-related dependencies like `scala-reflect` that doesn't need to be + * present at runtime + */ + def compileIvyDeps = T{ Agg.empty[Dep] } + /** + * Same as `ivyDeps`, but only present at runtime. Useful for e.g. + * selecting different versions of a dependency to use at runtime after your + * code has already been compiled + */ + def runIvyDeps = T{ Agg.empty[Dep] } + + /** + * Options to pass to the java compiler + */ + def javacOptions = T{ Seq.empty[String] } + + /** The direct dependencies of this module */ + def moduleDeps = Seq.empty[JavaModule] + + /** The direct and indirect dependencies of this module */ + def recursiveModuleDeps: Seq[JavaModule] = { + moduleDeps.flatMap(_.transitiveModuleDeps).distinct + } + + /** Like `recursiveModuleDeps` but also include the module itself */ + def transitiveModuleDeps: Seq[JavaModule] = { + Seq(this) ++ recursiveModuleDeps + } + + /** + * Additional jars, classfiles or resources to add to the classpath directly + * from disk rather than being downloaded from Maven Central or other package + * repositories + */ + def unmanagedClasspath = T{ Agg.empty[PathRef] } + + /** + * The transitive ivy dependencies of this module and all it's upstream modules + */ + def transitiveIvyDeps: T[Agg[Dep]] = T{ + ivyDeps() ++ Task.traverse(moduleDeps)(_.transitiveIvyDeps)().flatten + } + + /** + * The upstream compilation output of all this module's upstream modules + */ + def upstreamCompileOutput = T{ + Task.traverse(recursiveModuleDeps)(_.compile) + } + + /** + * The transitive version of `localClasspath` + */ + def transitiveLocalClasspath: T[Agg[PathRef]] = T{ + Task.traverse(moduleDeps)(m => + T.task{m.localClasspath() ++ m.transitiveLocalClasspath()} + )().flatten + } + + def mapDependencies = T.task{ d: coursier.Dependency => d } + + def resolveDeps(deps: Task[Agg[Dep]], sources: Boolean = false) = T.task{ + resolveDependencies( + repositories, + resolveCoursierDependency().apply(_), + deps(), + sources, + mapDependencies = Some(mapDependencies()) + ) + } + + + def repositories: Seq[Repository] = zincWorker.repositories + + /** + * What platform suffix to use for publishing, e.g. `_sjs` for Scala.js + * projects + */ + def platformSuffix = T{ "" } + + private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r + + /** + * What shell script to use to launch the executable generated by `assembly`. + * Defaults to a generic "universal" launcher that should work for Windows, + * OS-X and Linux + */ + def prependShellScript: T[String] = T{ + finalMainClassOpt().toOption match{ + case None => "" + case Some(cls) => + val isWin = scala.util.Properties.isWin + mill.modules.Jvm.launcherUniversalScript( + cls, + Agg("$0"), Agg("%~dpnx0"), + forkArgs() + ) + } + } + + def assemblyRules: Seq[Assembly.Rule] = Assembly.defaultRules + + /** + * The folders where the source files for this module live + */ + def sources = T.sources{ millSourcePath / 'src } + /** + * The folders where the resource files for this module live + */ + def resources = T.sources{ millSourcePath / 'resources } + /** + * Folders containing source files that are generated rather than + * hand-written; these files can be generated in this target itself, + * or can refer to files generated from other targets + */ + def generatedSources = T{ Seq.empty[PathRef] } + + /** + * The folders containing all source files fed into the compiler + */ + def allSources = T{ sources() ++ generatedSources() } + + /** + * All individual source files fed into the compiler + */ + def allSourceFiles = T{ + def isHiddenFile(path: os.Path) = path.last.startsWith(".") + for { + root <- allSources() + if os.exists(root.path) + path <- (if (os.isDir(root.path)) os.walk(root.path) else Seq(root.path)) + if os.isFile(path) && ((path.ext == "scala" || path.ext == "java") && !isHiddenFile(path)) + } yield PathRef(path) + } + + /** + * Compiles the current module to generate compiled classfiles/bytecode + */ + def compile: T[mill.scalalib.api.CompilationResult] = T.persistent{ + zincWorker.worker().compileJava( + upstreamCompileOutput(), + allSourceFiles().map(_.path), + compileClasspath().map(_.path), + javacOptions() + ) + } + + /** + * The output classfiles/resources from this module, excluding upstream + * modules and third-party dependencies + */ + def localClasspath = T{ + resources() ++ Agg(compile().classes) + } + + /** + * All classfiles and resources from upstream modules and dependencies + * necessary to compile this module + */ + def compileClasspath = T{ + transitiveLocalClasspath() ++ + resources() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{compileIvyDeps() ++ transitiveIvyDeps()})() + } + + /** + * All upstream classfiles and resources necessary to build and executable + * assembly, but without this module's contribution + */ + def upstreamAssemblyClasspath = T{ + transitiveLocalClasspath() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{runIvyDeps() ++ transitiveIvyDeps()})() + } + + /** + * All classfiles and resources from upstream modules and dependencies + * necessary to run this module's code after compilation + */ + def runClasspath = T{ + localClasspath() ++ + upstreamAssemblyClasspath() + } + + /** + * Build the assembly for upstream dependencies separate from the current + * classpath + * + * This should allow much faster assembly creation in the common case where + * upstream dependencies do not change + */ + def upstreamAssembly = T{ + createAssembly( + upstreamAssemblyClasspath().map(_.path), + mainClass(), + assemblyRules = assemblyRules + ) + } + + /** + * An executable uber-jar/assembly containing all the resources and compiled + * classfiles from this module and all it's upstream modules and dependencies + */ + def assembly = T{ + createAssembly( + Agg.from(localClasspath().map(_.path)), + finalMainClassOpt().toOption, + prependShellScript(), + Some(upstreamAssembly().path), + assemblyRules + ) + } + + /** + * A jar containing only this module's resources and compiled classfiles, + * without those from upstream modules and dependencies + */ + def jar = T{ + createJar( + localClasspath().map(_.path).filter(os.exists), + mainClass() + ) + } + + /** + * The documentation jar, containing all the Javadoc/Scaladoc HTML files, for + * publishing to Maven Central + */ + def docJar = T[PathRef] { + val outDir = T.ctx().dest + + val javadocDir = outDir / 'javadoc + os.makeDir.all(javadocDir) + + val files = for{ + ref <- allSources() + if os.exists(ref.path) + p <- (if (os.isDir(ref.path)) os.walk(ref.path) else Seq(ref.path)) + if os.isFile(p) && (p.ext == "java") + } yield p.toNIO.toString + + val options = Seq("-d", javadocDir.toNIO.toString) + + if (files.nonEmpty) Jvm.baseInteractiveSubprocess( + commandArgs = Seq( + "javadoc" + ) ++ options ++ + Seq( + "-classpath", + compileClasspath() + .map(_.path) + .filter(_.ext != "pom") + .mkString(java.io.File.pathSeparator) + ) ++ + files.map(_.toString), + envArgs = Map(), + workingDir = T.ctx().dest + ) + + createJar(Agg(javadocDir))(outDir) + } + + /** + * The source jar, containing only source code for publishing to Maven Central + */ + def sourceJar = T { + createJar((allSources() ++ resources()).map(_.path).filter(os.exists)) + } + + /** + * Any command-line parameters you want to pass to the forked JVM under `run`, + * `test` or `repl` + */ + def forkArgs = T{ Seq.empty[String] } + + /** + * Any environment variables you want to pass to the forked JVM under `run`, + * `test` or `repl` + */ + def forkEnv = T{ sys.env.toMap } + + /** + * Builds a command-line "launcher" file that can be used to run this module's + * code, without the Mill process. Useful for deployment & other places where + * you do not want a build tool running + */ + def launcher = T{ + Result.Success( + Jvm.createLauncher( + finalMainClass(), + runClasspath().map(_.path), + forkArgs() + ) + ) + } + + def ivyDepsTree(inverse: Boolean = false) = T.command { + val (flattened, resolution) = Lib.resolveDependenciesMetadata( + repositories, + resolveCoursierDependency().apply(_), + transitiveIvyDeps(), + Some(mapDependencies()) + ) + + println(coursier.util.Print.dependencyTree(flattened, resolution, + printExclusions = false, reverse = inverse)) + + Result.Success() + } + + /** + * Runs this module's code in-process within an isolated classloader. This is + * faster than `run`, but in exchange you have less isolation between runs + * since the code can dirty the parent Mill process and potentially leave it + * in a bad state. + */ + def runLocal(args: String*) = T.command { + Jvm.runLocal( + finalMainClass(), + runClasspath().map(_.path), + args + ) + } + + /** + * Runs this module's code in a subprocess and waits for it to finish + */ + def run(args: String*) = T.command{ + try Result.Success(Jvm.runSubprocess( + finalMainClass(), + runClasspath().map(_.path), + forkArgs(), + forkEnv(), + args, + workingDir = forkWorkingDir() + )) catch { case e: Exception => + Result.Failure("subprocess failed") + } + } + + private[this] def backgroundSetup(dest: os.Path) = { + val token = java.util.UUID.randomUUID().toString + val procId = dest / ".mill-background-process-id" + val procTombstone = dest / ".mill-background-process-tombstone" + // The backgrounded subprocesses poll the procId file, and kill themselves + // when the procId file is deleted. This deletion happens immediately before + // the body of these commands run, but we cannot be sure the subprocess has + // had time to notice. + // + // To make sure we wait for the previous subprocess to + // die, we make the subprocess write a tombstone file out when it kills + // itself due to procId being deleted, and we wait a short time on task-start + // to see if such a tombstone appears. If a tombstone appears, we can be sure + // the subprocess has killed itself, and can continue. If a tombstone doesn't + // appear in a short amount of time, we assume the subprocess exited or was + // killed via some other means, and continue anyway. + val start = System.currentTimeMillis() + while({ + if (os.exists(procTombstone)) { + Thread.sleep(10) + os.remove.all(procTombstone) + true + } else { + Thread.sleep(10) + System.currentTimeMillis() - start < 100 + } + })() + + os.write(procId, token) + os.write(procTombstone, token) + (procId, procTombstone, token) + } + + /** + * Runs this module's code in a background process, until it dies or + * `runBackground` is used again. This lets you continue using Mill while + * the process is running in the background: editing files, compiling, and + * only re-starting the background process when you're ready. + * + * You can also use `-w foo.runBackground` to make Mill watch for changes + * and automatically recompile your code & restart the background process + * when ready. This is useful when working on long-running server processes + * that would otherwise run forever + */ + def runBackground(args: String*) = T.command{ + val (procId, procTombstone, token) = backgroundSetup(T.ctx().dest) + try Result.Success(Jvm.runSubprocess( + "mill.scalalib.backgroundwrapper.BackgroundWrapper", + (runClasspath() ++ zincWorker.backgroundWrapperClasspath()).map(_.path), + forkArgs(), + forkEnv(), + Seq(procId.toString, procTombstone.toString, token, finalMainClass()) ++ args, + workingDir = forkWorkingDir(), + background = true + )) catch { case e: Exception => + Result.Failure("subprocess failed") + } + } + + /** + * Same as `runBackground`, but lets you specify a main class to run + */ + def runMainBackground(mainClass: String, args: String*) = T.command{ + val (procId, procTombstone, token) = backgroundSetup(T.ctx().dest) + try Result.Success(Jvm.runSubprocess( + "mill.scalalib.backgroundwrapper.BackgroundWrapper", + (runClasspath() ++ zincWorker.backgroundWrapperClasspath()).map(_.path), + forkArgs(), + forkEnv(), + Seq(procId.toString, procTombstone.toString, token, mainClass) ++ args, + workingDir = forkWorkingDir(), + background = true + )) catch { case e: Exception => + Result.Failure("subprocess failed") + } + } + + /** + * Same as `runLocal`, but lets you specify a main class to run + */ + def runMainLocal(mainClass: String, args: String*) = T.command { + Jvm.runLocal( + mainClass, + runClasspath().map(_.path), + args + ) + } + + /** + * Same as `run`, but lets you specify a main class to run + */ + def runMain(mainClass: String, args: String*) = T.command{ + try Result.Success(Jvm.runSubprocess( + mainClass, + runClasspath().map(_.path), + forkArgs(), + forkEnv(), + args, + workingDir = forkWorkingDir() + )) catch { case e: Exception => + Result.Failure("subprocess failed") + } + } + + // publish artifact with name "mill_2.12.4" instead of "mill_2.12" + + def artifactName: T[String] = millModuleSegments.parts.mkString("-") + + def artifactId: T[String] = artifactName() + + def intellijModulePath: os.Path = millSourcePath + + def forkWorkingDir = T{ ammonite.ops.pwd } + + /** + * Skip Idea project file generation. + */ + def skipIdea: Boolean = false +} + +trait TestModule extends JavaModule with TaskModule { + override def defaultCommandName() = "test" + /** + * What test frameworks to use. + */ + def testFrameworks: T[Seq[String]] + /** + * Discovers and runs the module's tests in a subprocess, reporting the + * results to the console + */ + def test(args: String*) = T.command{ + val outputPath = T.ctx().dest/"out.json" + + Jvm.runSubprocess( + mainClass = "mill.scalalib.TestRunner", + classPath = zincWorker.scalalibClasspath().map(_.path), + jvmArgs = forkArgs(), + envArgs = forkEnv(), + mainArgs = + Seq(testFrameworks().length.toString) ++ + testFrameworks() ++ + Seq(runClasspath().length.toString) ++ + runClasspath().map(_.path.toString) ++ + Seq(args.length.toString) ++ + args ++ + Seq(outputPath.toString, T.ctx().log.colored.toString, compile().classes.path.toString, T.ctx().home.toString), + workingDir = forkWorkingDir() + ) + + try { + val jsonOutput = ujson.read(outputPath.toIO) + val (doneMsg, results) = upickle.default.read[(String, Seq[TestRunner.Result])](jsonOutput) + TestModule.handleResults(doneMsg, results) + }catch{case e: Throwable => + Result.Failure("Test reporting failed: " + e) + } + + } + + /** + * Discovers and runs the module's tests in-process in an isolated classloader, + * reporting the results to the console + */ + def testLocal(args: String*) = T.command{ + val outputPath = T.ctx().dest/"out.json" + + val (doneMsg, results) = TestRunner.runTests( + TestRunner.frameworks(testFrameworks()), + runClasspath().map(_.path), + Agg(compile().classes.path), + args + ) + + TestModule.handleResults(doneMsg, results) + + } +} + +object TestModule{ + def handleResults(doneMsg: String, results: Seq[TestRunner.Result]) = { + + val badTests = results.filter(x => Set("Error", "Failure").contains(x.status)) + if (badTests.isEmpty) Result.Success((doneMsg, results)) + else { + val suffix = if (badTests.length == 1) "" else " and " + (badTests.length-1) + " more" + + Result.Failure( + badTests.head.fullyQualifiedName + " " + badTests.head.selector + suffix, + Some((doneMsg, results)) + ) + } + } +} + diff --git a/scalalib/src/Lib.scala b/scalalib/src/Lib.scala new file mode 100644 index 00000000..b8b253bd --- /dev/null +++ b/scalalib/src/Lib.scala @@ -0,0 +1,133 @@ +package mill +package scalalib + +import java.io.{File, FileInputStream} +import java.lang.annotation.Annotation +import java.lang.reflect.Modifier +import java.util.zip.ZipInputStream +import javax.tools.ToolProvider + +import ammonite.util.Util +import coursier.{Cache, Dependency, Fetch, Repository, Resolution} +import mill.scalalib.api.Util.isDotty +import mill.Agg +import mill.eval.{PathRef, Result} +import mill.modules.Jvm +import mill.api.Ctx +import sbt.testing._ + +import scala.collection.mutable + + +object Lib{ + def depToDependencyJava(dep: Dep, platformSuffix: String = ""): Dependency = { + assert(dep.cross.isConstant, s"Not a Java dependency: $dep") + depToDependency(dep, "", platformSuffix) + } + + def depToDependency(dep: Dep, scalaVersion: String, platformSuffix: String = ""): Dependency = + dep.toDependency( + binaryVersion = mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion), + fullVersion = scalaVersion, + platformSuffix = platformSuffix + ) + + def resolveDependenciesMetadata(repositories: Seq[Repository], + depToDependency: Dep => coursier.Dependency, + deps: TraversableOnce[Dep], + mapDependencies: Option[Dependency => Dependency] = None) = { + val depSeq = deps.toSeq + mill.modules.Jvm.resolveDependenciesMetadata( + repositories, + depSeq.map(depToDependency), + depSeq.filter(_.force).map(depToDependency), + mapDependencies + ) + } + /** + * Resolve dependencies using Coursier. + * + * We do not bother breaking this out into the separate ZincWorker classpath, + * because Coursier is already bundled with mill/Ammonite to support the + * `import $ivy` syntax. + */ + def resolveDependencies(repositories: Seq[Repository], + depToDependency: Dep => coursier.Dependency, + deps: TraversableOnce[Dep], + sources: Boolean = false, + mapDependencies: Option[Dependency => Dependency] = None): Result[Agg[PathRef]] = { + val depSeq = deps.toSeq + mill.modules.Jvm.resolveDependencies( + repositories, + depSeq.map(depToDependency), + depSeq.filter(_.force).map(depToDependency), + sources, + mapDependencies + ) + } + def scalaCompilerIvyDeps(scalaOrganization: String, scalaVersion: String) = + if (mill.scalalib.api.Util.isDotty(scalaVersion)) + Agg(ivy"$scalaOrganization::dotty-compiler:$scalaVersion".forceVersion()) + else + Agg( + ivy"$scalaOrganization:scala-compiler:$scalaVersion".forceVersion(), + ivy"$scalaOrganization:scala-reflect:$scalaVersion".forceVersion() + ) + + def scalaRuntimeIvyDeps(scalaOrganization: String, scalaVersion: String) = Agg[Dep]( + ivy"$scalaOrganization:scala-library:$scalaVersion".forceVersion() + ) + + def listClassFiles(base: os.Path): Iterator[String] = { + if (os.isDir(base)) os.walk(base).toIterator.filter(_.ext == "class").map(_.relativeTo(base).toString) + else { + val zip = new ZipInputStream(new FileInputStream(base.toIO)) + Iterator.continually(zip.getNextEntry).takeWhile(_ != null).map(_.getName).filter(_.endsWith(".class")) + } + } + + def discoverTests(cl: ClassLoader, framework: Framework, classpath: Agg[os.Path]) = { + + val fingerprints = framework.fingerprints() + + val testClasses = classpath.flatMap { base => + // Don't blow up if there are no classfiles representing + // the tests to run Instead just don't run anything + if (!os.exists(base)) Nil + else listClassFiles(base).flatMap { path => + val cls = cl.loadClass(path.stripSuffix(".class").replace('/', '.')) + val publicConstructorCount = + cls.getConstructors.count(c => c.getParameterCount == 0 && Modifier.isPublic(c.getModifiers)) + + if (Modifier.isAbstract(cls.getModifiers) || cls.isInterface || publicConstructorCount > 1) { + None + } else { + (cls.getName.endsWith("$"), publicConstructorCount == 0) match{ + case (true, true) => matchFingerprints(cl, cls, fingerprints, isModule = true) + case (false, false) => matchFingerprints(cl, cls, fingerprints, isModule = false) + case _ => None + } + } + } + } + + testClasses + } + def matchFingerprints(cl: ClassLoader, cls: Class[_], fingerprints: Array[Fingerprint], isModule: Boolean) = { + fingerprints.find { + case f: SubclassFingerprint => + f.isModule == isModule && + cl.loadClass(f.superclassName()).isAssignableFrom(cls) + + case f: AnnotatedFingerprint => + val annotationCls = cl.loadClass(f.annotationName()).asInstanceOf[Class[Annotation]] + f.isModule == isModule && + ( + cls.isAnnotationPresent(annotationCls) || + cls.getDeclaredMethods.exists(_.isAnnotationPresent(annotationCls)) + ) + + }.map { f => (cls, f) } + } + +} diff --git a/scalalib/src/MiscModule.scala b/scalalib/src/MiscModule.scala new file mode 100644 index 00000000..c6449d6e --- /dev/null +++ b/scalalib/src/MiscModule.scala @@ -0,0 +1,101 @@ +package mill +package scalalib + +import mill.define.Cross.Resolver +import mill.define.{Cross, Task} +import mill.eval.{PathRef, Result} +import mill.util.Loose.Agg +object CrossModuleBase{ + def scalaVersionPaths(scalaVersion: String, f: String => os.Path) = { + for(segments <- scalaVersion.split('.').inits.filter(_.nonEmpty)) + yield PathRef(f(segments.mkString("."))) + } +} +trait CrossModuleBase extends ScalaModule { + def crossScalaVersion: String + def scalaVersion = T{ crossScalaVersion } + + override def millSourcePath = super.millSourcePath / ammonite.ops.up + implicit def crossSbtModuleResolver: Resolver[CrossModuleBase] = new Resolver[CrossModuleBase]{ + def resolve[V <: CrossModuleBase](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(",") + ) + ) + } + } +} + +trait CrossScalaModule extends ScalaModule with CrossModuleBase{ outer => + override def sources = T.sources{ + super.sources() ++ + CrossModuleBase.scalaVersionPaths(crossScalaVersion, s => millSourcePath / s"src-$s" ) + } + + trait Tests extends super.Tests { + override def sources = T.sources{ + super.sources() ++ + CrossModuleBase.scalaVersionPaths(crossScalaVersion, s => millSourcePath / s"src-$s" ) + } + } +} + +trait MavenTests extends TestModule{ + override def sources = T.sources( + millSourcePath / 'src / 'test / 'scala, + millSourcePath / 'src / 'test / 'java + ) + override def resources = T.sources{ millSourcePath / 'src / 'test / 'resources } +} +trait MavenModule extends JavaModule{outer => + + override def sources = T.sources( + millSourcePath / 'src / 'main / 'scala, + millSourcePath / 'src / 'main / 'java + ) + override def resources = T.sources{ millSourcePath / 'src / 'main / 'resources } + trait Tests extends super.Tests with MavenTests { + override def millSourcePath = outer.millSourcePath + override def intellijModulePath = outer.millSourcePath / 'src / 'test + } +} + +trait SbtModule extends MavenModule with ScalaModule{ outer => + trait Tests extends super.Tests with MavenTests { + override def millSourcePath = outer.millSourcePath + override def intellijModulePath = outer.millSourcePath / 'src / 'test + } +} + +trait CrossSbtModule extends SbtModule with CrossModuleBase{ outer => + + override def sources = T.sources{ + super.sources() ++ + CrossModuleBase.scalaVersionPaths( + crossScalaVersion, + s => millSourcePath / 'src / 'main / s"scala-$s" + ) + + } + trait Tests extends super.Tests { + override def millSourcePath = outer.millSourcePath + override def sources = T.sources{ + super.sources() ++ + CrossModuleBase.scalaVersionPaths( + crossScalaVersion, + s => millSourcePath / 'src / 'test / s"scala-$s" + ) + } + } +} + + diff --git a/scalalib/src/PublishModule.scala b/scalalib/src/PublishModule.scala new file mode 100644 index 00000000..588781f4 --- /dev/null +++ b/scalalib/src/PublishModule.scala @@ -0,0 +1,124 @@ +package mill +package scalalib + +import mill.define.{ExternalModule, Task} +import mill.api.PathRef +import mill.scalalib.publish.{Artifact, SonatypePublisher} + +/** + * Configuration necessary for publishing a Scala module to Maven Central or similar + */ +trait PublishModule extends JavaModule { outer => + import mill.scalalib.publish._ + + override def moduleDeps = Seq.empty[PublishModule] + + def pomSettings: T[PomSettings] + def publishVersion: T[String] + + def publishSelfDependency = T { + Artifact(pomSettings().organization, artifactId(), publishVersion()) + } + + def publishXmlDeps = T.task { + val ivyPomDeps = ivyDeps().map(resolvePublishDependency().apply(_)) + val modulePomDeps = Task.sequence(moduleDeps.map(_.publishSelfDependency))() + ivyPomDeps ++ modulePomDeps.map(Dependency(_, Scope.Compile)) + } + def pom = T { + val pom = Pom(artifactMetadata(), publishXmlDeps(), artifactId(), pomSettings()) + val pomPath = T.ctx().dest / s"${artifactId()}-${publishVersion()}.pom" + os.write.over(pomPath, pom) + PathRef(pomPath) + } + + def ivy = T { + val ivy = Ivy(artifactMetadata(), publishXmlDeps()) + val ivyPath = T.ctx().dest / "ivy.xml" + os.write.over(ivyPath, ivy) + PathRef(ivyPath) + } + + def artifactMetadata: T[Artifact] = T { + Artifact(pomSettings().organization, artifactId(), publishVersion()) + } + + def publishLocal(): define.Command[Unit] = T.command { + LocalPublisher.publish( + jar = jar().path, + sourcesJar = sourceJar().path, + docJar = docJar().path, + pom = pom().path, + ivy = ivy().path, + artifact = artifactMetadata() + ) + } + + def sonatypeUri: String = "https://oss.sonatype.org/service/local" + + def sonatypeSnapshotUri: String = "https://oss.sonatype.org/content/repositories/snapshots" + + def publishArtifacts = T { + val baseName = s"${artifactId()}-${publishVersion()}" + PublishModule.PublishData( + artifactMetadata(), + Seq( + jar() -> s"$baseName.jar", + sourceJar() -> s"$baseName-sources.jar", + docJar() -> s"$baseName-javadoc.jar", + pom() -> s"$baseName.pom" + ) + ) + } + + def publish(sonatypeCreds: String, + gpgPassphrase: String = null, + signed: Boolean = true, + release: Boolean): define.Command[Unit] = T.command { + val PublishModule.PublishData(artifactInfo, artifacts) = publishArtifacts() + new SonatypePublisher( + sonatypeUri, + sonatypeSnapshotUri, + sonatypeCreds, + Option(gpgPassphrase), + signed, + T.ctx().log + ).publish(artifacts.map{case (a, b) => (a.path, b)}, artifactInfo, release) + } +} + +object PublishModule extends ExternalModule { + case class PublishData(meta: Artifact, payload: Seq[(PathRef, String)]) + + object PublishData{ + implicit def jsonify: upickle.default.ReadWriter[PublishData] = upickle.default.macroRW + } + + def publishAll(sonatypeCreds: String, + gpgPassphrase: String = null, + publishArtifacts: mill.main.Tasks[PublishModule.PublishData], + release: Boolean = false, + sonatypeUri: String = "https://oss.sonatype.org/service/local", + sonatypeSnapshotUri: String = "https://oss.sonatype.org/content/repositories/snapshots", + signed: Boolean = true) = T.command { + + val x: Seq[(Seq[(os.Path, String)], Artifact)] = Task.sequence(publishArtifacts.value)().map{ + case PublishModule.PublishData(a, s) => (s.map{case (p, f) => (p.path, f)}, a) + } + new SonatypePublisher( + sonatypeUri, + sonatypeSnapshotUri, + sonatypeCreds, + Option(gpgPassphrase), + signed, + T.ctx().log + ).publishAll( + release, + x:_* + ) + } + + implicit def millScoptTargetReads[T] = new mill.main.Tasks.Scopt[T]() + + lazy val millDiscover: mill.define.Discover[this.type] = mill.define.Discover[this.type] +} diff --git a/scalalib/src/ScalaModule.scala b/scalalib/src/ScalaModule.scala new file mode 100644 index 00000000..9d669bf4 --- /dev/null +++ b/scalalib/src/ScalaModule.scala @@ -0,0 +1,275 @@ +package mill +package scalalib + +import coursier.Repository +import mill.define.{Target, Task, TaskModule} +import mill.eval.{PathRef, Result} +import mill.modules.Jvm +import mill.modules.Jvm.createJar +import mill.scalalib.api.Util.isDotty +import Lib._ +import mill.util.Loose.Agg +import mill.api.DummyInputStream + +/** + * Core configuration required to compile a single Scala compilation target + */ +trait ScalaModule extends JavaModule { outer => + trait Tests extends TestModule with ScalaModule{ + override def scalaOrganization = outer.scalaOrganization() + def scalaVersion = outer.scalaVersion() + override def repositories = outer.repositories + override def scalacPluginIvyDeps = outer.scalacPluginIvyDeps + override def scalacOptions = outer.scalacOptions + override def javacOptions = outer.javacOptions + override def zincWorker = outer.zincWorker + override def moduleDeps: Seq[JavaModule] = Seq(outer) + } + + /** + * What Scala organization to use + * @return + */ + def scalaOrganization: T[String] = T { + if (isDotty(scalaVersion())) + "ch.epfl.lamp" + else + "org.scala-lang" + } + + /** + * What version of Scala to use + */ + def scalaVersion: T[String] + + override def mapDependencies = T.task{ d: coursier.Dependency => + val artifacts = + if (isDotty(scalaVersion())) + Set("dotty-library", "dotty-compiler") + else + Set("scala-library", "scala-compiler", "scala-reflect") + if (!artifacts(d.module.name)) d + else d.copy(module = d.module.copy(organization = scalaOrganization()), version = scalaVersion()) + } + + override def resolveCoursierDependency: Task[Dep => coursier.Dependency] = T.task{ + Lib.depToDependency(_: Dep, scalaVersion(), platformSuffix()) + } + + override def resolvePublishDependency: Task[Dep => publish.Dependency] = T.task{ + publish.Artifact.fromDep( + _: Dep, + scalaVersion(), + mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion()), + platformSuffix() + ) + } + + /** + * Allows you to make use of Scala compiler plugins from maven central + */ + def scalacPluginIvyDeps = T{ Agg.empty[Dep] } + + def scalaDocPluginIvyDeps = T{ scalacPluginIvyDeps() } + + /** + * Command-line options to pass to the Scala compiler + */ + def scalacOptions = T{ Seq.empty[String] } + + def scalaDocOptions = T{ scalacOptions() } + + private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r + + def scalaCompilerBridgeSources = T { + val (scalaVersion0, scalaBinaryVersion0) = scalaVersion() match { + case Milestone213(_, _) => ("2.13.0-M2", "2.13.0-M2") + case _ => (scalaVersion(), mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion())) + } + + val (bridgeDep, bridgeName, bridgeVersion) = + if (isDotty(scalaVersion0)) { + val org = scalaOrganization() + val name = "dotty-sbt-bridge" + val version = scalaVersion() + (ivy"$org:$name:$version", name, version) + } else { + val org = "org.scala-sbt" + val name = "compiler-bridge" + val version = Versions.zinc + (ivy"$org::$name:$version", s"${name}_$scalaBinaryVersion0", version) + } + + resolveDependencies( + repositories, + Lib.depToDependency(_, scalaVersion0, platformSuffix()), + Seq(bridgeDep), + sources = true + ).map(deps => + mill.scalalib.api.Util.grepJar(deps.map(_.path), bridgeName, bridgeVersion, sources = true) + ) + } + + /** + * The local classpath of Scala compiler plugins on-disk; you can add + * additional jars here if you have some copiler plugin that isn't present + * on maven central + */ + def scalacPluginClasspath: T[Agg[PathRef]] = T { + resolveDeps(scalacPluginIvyDeps)() + } + + /** + * The ivy coordinates of Scala's own standard library + */ + def scalaDocPluginClasspath: T[Agg[PathRef]] = T { + resolveDeps(scalaDocPluginIvyDeps)() + } + + def scalaLibraryIvyDeps = T{ scalaRuntimeIvyDeps(scalaOrganization(), scalaVersion()) } + + /** + * Classpath of the Scala Compiler & any compiler plugins + */ + def scalaCompilerClasspath: T[Agg[PathRef]] = T{ + resolveDeps( + T.task{ + scalaCompilerIvyDeps(scalaOrganization(), scalaVersion()) ++ + scalaRuntimeIvyDeps(scalaOrganization(), scalaVersion()) + } + )() + } + override def compileClasspath = T{ + transitiveLocalClasspath() ++ + resources() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{compileIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps()})() + } + + override def upstreamAssemblyClasspath = T{ + transitiveLocalClasspath() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{runIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps()})() + } + + override def compile: T[mill.scalalib.api.CompilationResult] = T.persistent{ + zincWorker.worker().compileMixed( + upstreamCompileOutput(), + allSourceFiles().map(_.path), + compileClasspath().map(_.path), + javacOptions(), + scalaVersion(), + scalacOptions(), + scalaCompilerBridgeSources(), + scalaCompilerClasspath().map(_.path), + scalacPluginClasspath().map(_.path), + ) + } + + override def docJar = T { + val outDir = T.ctx().dest + + val javadocDir = outDir / 'javadoc + os.makeDir.all(javadocDir) + + val files = allSourceFiles().map(_.path.toString) + + val pluginOptions = scalaDocPluginClasspath().map(pluginPathRef => s"-Xplugin:${pluginPathRef.path}") + val compileCp = compileClasspath().filter(_.path.ext != "pom").map(_.path) + val options = Seq( + "-d", javadocDir.toNIO.toString, + "-classpath", compileCp.mkString(":") + ) ++ + pluginOptions ++ + scalaDocOptions() + + if (files.isEmpty) Result.Success(createJar(Agg(javadocDir))(outDir)) + else { + zincWorker.worker().docJar( + scalaVersion(), + scalaCompilerBridgeSources(), + scalaCompilerClasspath().map(_.path), + scalacPluginClasspath().map(_.path), + files ++ options + ) match{ + case true => Result.Success(createJar(Agg(javadocDir))(outDir)) + case false => Result.Failure("docJar generation failed") + } + } + } + + /** + * Opens up a Scala console with your module and all dependencies present, + * for you to test and operate your code interactively + */ + def console() = T.command{ + if (T.ctx().log.inStream == DummyInputStream){ + Result.Failure("repl needs to be run with the -i/--interactive flag") + }else{ + Jvm.runSubprocess( + mainClass = + if (isDotty(scalaVersion())) + "dotty.tools.repl.Main" + else + "scala.tools.nsc.MainGenericRunner", + classPath = runClasspath().map(_.path) ++ scalaCompilerClasspath().map(_.path), + mainArgs = Seq("-usejavacp"), + workingDir = os.pwd + ) + Result.Success() + } + } + + /** + * Dependencies that are necessary to run the Ammonite Scala REPL + */ + def ammoniteReplClasspath = T{ + localClasspath() ++ + transitiveLocalClasspath() ++ + unmanagedClasspath() ++ + resolveDeps(T.task{ + runIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps() ++ + Agg(ivy"com.lihaoyi:::ammonite:${Versions.ammonite}") + })() + } + + /** + * Opens up an Ammonite Scala REPL with your module and all dependencies present, + * for you to test and operate your code interactively + */ + def repl(replOptions: String*) = T.command{ + if (T.ctx().log.inStream == DummyInputStream){ + Result.Failure("repl needs to be run with the -i/--interactive flag") + }else{ + Jvm.runSubprocess( + mainClass = "ammonite.Main", + classPath = ammoniteReplClasspath().map(_.path), + mainArgs = replOptions, + workingDir = os.pwd + ) + Result.Success() + } + + } + + /** + * Whether to publish artifacts with name "mill_2.12.4" instead of "mill_2.12" + */ + def crossFullScalaVersion: T[Boolean] = false + + /** + * What Scala version string to use when publishing + */ + def artifactScalaVersion: T[String] = T { + if (crossFullScalaVersion()) scalaVersion() + else mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion()) + } + + /** + * The suffix appended to the artifact IDs during publishing + */ + def artifactSuffix: T[String] = s"_${artifactScalaVersion()}" + + override def artifactId: T[String] = artifactName() + artifactSuffix() + +} diff --git a/scalalib/src/TestRunner.scala b/scalalib/src/TestRunner.scala new file mode 100644 index 00000000..42e65d63 --- /dev/null +++ b/scalalib/src/TestRunner.scala @@ -0,0 +1,153 @@ +package mill.scalalib +import ammonite.util.Colors +import mill.Agg +import mill.modules.Jvm +import mill.scalalib.Lib.discoverTests +import mill.util.{Ctx, PrintLogger} +import mill.util.JsonFormatters._ +import sbt.testing._ + +import scala.collection.mutable +object TestRunner { + + + def main(args: Array[String]): Unit = { + try{ + var i = 0 + def readArray() = { + val count = args(i).toInt + val slice = args.slice(i + 1, i + count + 1) + i = i + count + 1 + slice + } + val frameworks = readArray() + val classpath = readArray() + val arguments = readArray() + val outputPath = args(i + 0) + val colored = args(i + 1) + val testCp = args(i + 2) + val homeStr = args(i + 3) + val ctx = new Ctx.Log with Ctx.Home { + val log = PrintLogger( + colored == "true", + true, + if(colored == "true") Colors.Default + else Colors.BlackWhite, + System.out, + System.err, + System.err, + System.in, + debugEnabled = false + ) + val home = os.Path(homeStr) + } + val result = runTests( + frameworkInstances = TestRunner.frameworks(frameworks), + entireClasspath = Agg.from(classpath.map(os.Path(_))), + testClassfilePath = Agg(os.Path(testCp)), + args = arguments + )(ctx) + + // Clear interrupted state in case some badly-behaved test suite + // dirtied the thread-interrupted flag and forgot to clean up. Otherwise + // that flag causes writing the results to disk to fail + Thread.interrupted() + ammonite.ops.write(os.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 runTests(frameworkInstances: ClassLoader => Seq[sbt.testing.Framework], + entireClasspath: Agg[os.Path], + testClassfilePath: Agg[os.Path], + args: Seq[String]) + (implicit ctx: Ctx.Log with Ctx.Home): (String, Seq[mill.scalalib.TestRunner.Result]) = { + //Leave the context class loader set and open so that shutdown hooks can access it + Jvm.inprocess(entireClasspath, classLoaderOverrideSbtTesting = true, isolated = true, closeContextClassLoaderWhenDone = false, cl => { + val frameworks = frameworkInstances(cl) + + val events = mutable.Buffer.empty[Event] + + val doneMessages = frameworks.map{ framework => + val runner = framework.runner(args.toArray, Array[String](), cl) + + val testClasses = discoverTests(cl, framework, testClassfilePath) + + val tasks = runner.tasks( + for ((cls, fingerprint) <- testClasses.toArray) + yield new TaskDef(cls.getName.stripSuffix("$"), fingerprint, true, Array(new SuiteSelector)) + ) + + val taskQueue = tasks.to[mutable.Queue] + while (taskQueue.nonEmpty){ + val next = taskQueue.dequeue().execute( + new EventHandler { + def handle(event: Event) = events.append(event) + }, + Array( + new Logger { + def debug(msg: String) = ctx.log.outputStream.println(msg) + + def error(msg: String) = ctx.log.outputStream.println(msg) + + def ansiCodesSupported() = true + + def warn(msg: String) = ctx.log.outputStream.println(msg) + + def trace(t: Throwable) = t.printStackTrace(ctx.log.outputStream) + + def info(msg: String) = ctx.log.outputStream.println(msg) + }) + ) + taskQueue.enqueue(next:_*) + } + runner.done() + } + + val results = for(e <- events) yield { + val ex = if (e.throwable().isDefined) Some(e.throwable().get) else None + mill.scalalib.TestRunner.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().toString, + ex.map(_.getClass.getName), + ex.map(_.getMessage), + ex.map(_.getStackTrace) + ) + } + + (doneMessages.mkString("\n"), results) + }) + } + + def frameworks(frameworkNames: Seq[String])(cl: ClassLoader): Seq[sbt.testing.Framework] = { + frameworkNames.map { name => + cl.loadClass(name).newInstance().asInstanceOf[sbt.testing.Framework] + } + } + + case class Result(fullyQualifiedName: String, + selector: String, + duration: Long, + status: String, + exceptionName: Option[String] = None, + exceptionMsg: Option[String] = None, + exceptionTrace: Option[Seq[StackTraceElement]] = None) + + object Result{ + implicit def resultRW: upickle.default.ReadWriter[Result] = upickle.default.macroRW[Result] + } +} diff --git a/scalalib/src/Versions.scala b/scalalib/src/Versions.scala new file mode 100644 index 00000000..e7eaf847 --- /dev/null +++ b/scalalib/src/Versions.scala @@ -0,0 +1,8 @@ +package mill.scalalib + +object Versions { + // Keep synchronized with ammonite dependency in core in build.sc + val ammonite = "1.5.0" + // Keep synchronized with zinc dependency in scalalib.worker in build.sc + val zinc = "1.2.1" +} diff --git a/scalalib/src/ZincWorkerModule.scala b/scalalib/src/ZincWorkerModule.scala new file mode 100644 index 00000000..5ca824ce --- /dev/null +++ b/scalalib/src/ZincWorkerModule.scala @@ -0,0 +1,56 @@ +package mill.scalalib + +import coursier.Cache +import coursier.maven.MavenRepository +import mill.Agg +import mill.T +import mill.define.{Discover, Worker} +import mill.scalalib.Lib.resolveDependencies +import mill.util.Loose +import mill.util.JsonFormatters._ + +object ZincWorkerModule extends mill.define.ExternalModule with ZincWorkerModule{ + lazy val millDiscover = Discover[this.type] +} +trait ZincWorkerModule extends mill.Module{ + def repositories = Seq( + Cache.ivy2Local, + MavenRepository("https://repo1.maven.org/maven2"), + MavenRepository("https://oss.sonatype.org/content/repositories/releases") + ) + + def classpath = T{ + mill.modules.Util.millProjectModule("MILL_SCALA_WORKER", "mill-scalalib-worker", repositories) + } + + def scalalibClasspath = T{ + mill.modules.Util.millProjectModule("MILL_SCALA_LIB", "mill-scalalib", repositories) + } + + def backgroundWrapperClasspath = T{ + mill.modules.Util.millProjectModule( + "MILL_BACKGROUNDWRAPPER", "mill-scalalib-backgroundwrapper", + repositories, artifactSuffix = "" + ) + } + + def worker: Worker[mill.scalalib.api.ZincWorkerApi] = T.worker{ + val cl = mill.api.ClassLoader.create( + classpath().map(_.path.toNIO.toUri.toURL).toVector, + getClass.getClassLoader + ) + val cls = cl.loadClass("mill.scalalib.worker.ZincWorkerImpl") + val instance = cls.getConstructor(classOf[mill.api.Ctx], classOf[Array[String]]) + .newInstance(T.ctx(), compilerInterfaceClasspath().map(_.path.toString).toArray[String]) + instance.asInstanceOf[mill.scalalib.api.ZincWorkerApi] + } + + def compilerInterfaceClasspath = T{ + resolveDependencies( + repositories, + Lib.depToDependency(_, "2.12.4", ""), + Seq(ivy"org.scala-sbt:compiler-interface:${Versions.zinc}") + ) + } + +} diff --git a/scalalib/src/dependency/DependencyUpdatesImpl.scala b/scalalib/src/dependency/DependencyUpdatesImpl.scala new file mode 100644 index 00000000..3bb94202 --- /dev/null +++ b/scalalib/src/dependency/DependencyUpdatesImpl.scala @@ -0,0 +1,52 @@ +package mill.scalalib.dependency + +import mill.define._ +import mill.scalalib.dependency.updates.{ + DependencyUpdates, + ModuleDependenciesUpdates, + UpdatesFinder +} +import mill.scalalib.dependency.versions.VersionsFinder +import mill.api.Ctx.{Home, Log} + +object DependencyUpdatesImpl { + + def apply(ctx: Log with Home, + rootModule: BaseModule, + discover: Discover[_], + allowPreRelease: Boolean): Unit = { + + // 1. Find all available versions for each dependency + val allDependencyVersions = VersionsFinder.findVersions(ctx, rootModule) + + // 2. Extract updated versions from all available versions + val allUpdates = allDependencyVersions.map { dependencyVersions => + UpdatesFinder.findUpdates(dependencyVersions, allowPreRelease) + } + + // 3. Print the results + showAllUpdates(allUpdates) + } + + private def showAllUpdates(updates: Seq[ModuleDependenciesUpdates]): Unit = + updates.foreach { dependencyUpdates => + val module = dependencyUpdates.module.toString + val actualUpdates = + dependencyUpdates.dependencies.filter(_.updates.nonEmpty) + if (actualUpdates.isEmpty) { + println(s"No dependency updates found for $module") + } else { + println(s"Found ${actualUpdates.length} dependency update for $module") + showUpdates(actualUpdates) + } + } + + private def showUpdates(updates: Seq[DependencyUpdates]): Unit = + updates.foreach { dependencyUpdate => + val module = s"${dependencyUpdate.dependency.module}" + val allVersions = + (dependencyUpdate.currentVersion +: dependencyUpdate.updates.toList) + .mkString(" -> ") + println(s" $module : $allVersions") + } +} diff --git a/scalalib/src/dependency/metadata/MavenMetadataLoader.scala b/scalalib/src/dependency/metadata/MavenMetadataLoader.scala new file mode 100644 index 00000000..491911bf --- /dev/null +++ b/scalalib/src/dependency/metadata/MavenMetadataLoader.scala @@ -0,0 +1,21 @@ +package mill.scalalib.dependency.metadata + +import coursier.Cache +import coursier.maven.MavenRepository +import coursier.util.Task +import mill.scalalib.dependency.versions.Version + +private[dependency] final case class MavenMetadataLoader(mavenRepo: MavenRepository) + extends MetadataLoader { + + private val fetch = Cache.fetch[Task]() + + override def getVersions(module: coursier.Module): List[Version] = { + import scala.concurrent.ExecutionContext.Implicits.global + // TODO fallback to 'versionsFromListing' if 'versions' doesn't work? (needs to be made public in coursier first) + val allVersions = mavenRepo.versions(module, fetch).run.unsafeRun + allVersions + .map(_.available.map(Version(_))) + .getOrElse(List.empty) + } +} diff --git a/scalalib/src/dependency/metadata/MetadataLoader.scala b/scalalib/src/dependency/metadata/MetadataLoader.scala new file mode 100644 index 00000000..20271f0e --- /dev/null +++ b/scalalib/src/dependency/metadata/MetadataLoader.scala @@ -0,0 +1,7 @@ +package mill.scalalib.dependency.metadata + +import mill.scalalib.dependency.versions.Version + +private[dependency] trait MetadataLoader { + def getVersions(module: coursier.Module): Seq[Version] +} diff --git a/scalalib/src/dependency/metadata/MetadataLoaderFactory.scala b/scalalib/src/dependency/metadata/MetadataLoaderFactory.scala new file mode 100644 index 00000000..4495d6b0 --- /dev/null +++ b/scalalib/src/dependency/metadata/MetadataLoaderFactory.scala @@ -0,0 +1,11 @@ +package mill.scalalib.dependency.metadata + +import coursier.Repository +import coursier.maven.MavenRepository + +private[dependency] object MetadataLoaderFactory { + def apply(repo: Repository): Option[MetadataLoader] = repo match { + case mavenRepo: MavenRepository => Some(MavenMetadataLoader(mavenRepo)) + case _ => None + } +} diff --git a/scalalib/src/dependency/updates/ModuleDependenciesUpdates.scala b/scalalib/src/dependency/updates/ModuleDependenciesUpdates.scala new file mode 100644 index 00000000..a989cd31 --- /dev/null +++ b/scalalib/src/dependency/updates/ModuleDependenciesUpdates.scala @@ -0,0 +1,15 @@ +package mill.scalalib.dependency.updates + +import mill.scalalib.JavaModule +import mill.scalalib.dependency.versions.Version + +import scala.collection.SortedSet + +private[dependency] final case class ModuleDependenciesUpdates( + module: JavaModule, + dependencies: Seq[DependencyUpdates]) + +private[dependency] final case class DependencyUpdates( + dependency: coursier.Dependency, + currentVersion: Version, + updates: SortedSet[Version]) diff --git a/scalalib/src/dependency/updates/UpdatesFinder.scala b/scalalib/src/dependency/updates/UpdatesFinder.scala new file mode 100644 index 00000000..3430592f --- /dev/null +++ b/scalalib/src/dependency/updates/UpdatesFinder.scala @@ -0,0 +1,75 @@ +/* + * This file contains code originally published under the following license: + * + * Copyright (c) 2012, Roman Timushev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package mill.scalalib.dependency.updates + +import mill.scalalib.dependency.versions._ + +import scala.collection.SortedSet + +private[dependency] object UpdatesFinder { + + import scala.Ordered._ + + def findUpdates(dependencyVersions: ModuleDependenciesVersions, + allowPreRelease: Boolean): ModuleDependenciesUpdates = { + val dependencies = + dependencyVersions.dependencies.map { dependencyVersion => + findUpdates(dependencyVersion, allowPreRelease) + } + ModuleDependenciesUpdates(dependencyVersions.module, dependencies) + } + + def findUpdates(dependencyVersion: DependencyVersions, + allowPreRelease: Boolean): DependencyUpdates = { + val current = dependencyVersion.currentVersion + val versions = dependencyVersion.allversions.to[SortedSet] + + val updates = versions + .filter(isUpdate(current)) + .filterNot(lessStable(current, allowPreRelease)) + + DependencyUpdates(dependencyVersion.dependency, + dependencyVersion.currentVersion, + updates) + } + + private def lessStable(current: Version, allowPreRelease: Boolean)( + another: Version): Boolean = (current, another) match { + case (ReleaseVersion(_), ReleaseVersion(_)) => false + case (SnapshotVersion(_, _, _), _) => false + case (_, SnapshotVersion(_, _, _)) => true + case (ReleaseVersion(_), PreReleaseVersion(_, _)) => !allowPreRelease + case (ReleaseVersion(_), PreReleaseBuildVersion(_, _, _)) => + !allowPreRelease + case (ReleaseVersion(_), _) => true + case (_, _) => false + } + + private def isUpdate(current: Version) = current < _ +} diff --git a/scalalib/src/dependency/versions/ModuleDependenciesVersions.scala b/scalalib/src/dependency/versions/ModuleDependenciesVersions.scala new file mode 100644 index 00000000..12d57059 --- /dev/null +++ b/scalalib/src/dependency/versions/ModuleDependenciesVersions.scala @@ -0,0 +1,12 @@ +package mill.scalalib.dependency.versions + +import mill.scalalib.JavaModule + +private[dependency] final case class ModuleDependenciesVersions( + module: JavaModule, + dependencies: Seq[DependencyVersions]) + +private[dependency] final case class DependencyVersions( + dependency: coursier.Dependency, + currentVersion: Version, + allversions: Set[Version]) diff --git a/scalalib/src/dependency/versions/Version.scala b/scalalib/src/dependency/versions/Version.scala new file mode 100644 index 00000000..a2719023 --- /dev/null +++ b/scalalib/src/dependency/versions/Version.scala @@ -0,0 +1,227 @@ +/* + * This file contains code originally published under the following license: + * + * Copyright (c) 2012, Roman Timushev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package mill.scalalib.dependency.versions + +import scala.util.matching.Regex +import scala.util.matching.Regex.Groups + +private[dependency] sealed trait Version { + def major: Long + + def minor: Long + + def patch: Long +} + +private[dependency] case class ValidVersion(text: String, + releasePart: List[Long], + preReleasePart: List[String], + buildPart: List[String]) + extends Version { + def major: Long = releasePart.headOption getOrElse 0 + + def minor: Long = releasePart.drop(1).headOption getOrElse 1 + + def patch: Long = releasePart.drop(2).headOption getOrElse 1 + + override def toString: String = text +} + +private[dependency] case class InvalidVersion(text: String) extends Version { + def major: Long = -1 + + def minor: Long = -1 + + def patch: Long = -1 +} + +private[dependency] object ReleaseVersion { + private val releaseKeyword: Regex = "(?i)final|release".r + + def unapply(v: Version): Option[List[Long]] = v match { + case ValidVersion(_, releasePart, Nil, Nil) => Some(releasePart) + case ValidVersion(_, releasePart, releaseKeyword() :: Nil, Nil) => + Some(releasePart) + case _ => None + } +} + +private[dependency] object PreReleaseVersion { + def unapply(v: Version): Option[(List[Long], List[String])] = v match { + case ValidVersion(_, releasePart, preReleasePart, Nil) + if preReleasePart.nonEmpty => + Some(releasePart, preReleasePart) + case _ => None + } +} + +private[dependency] object PreReleaseBuildVersion { + def unapply(v: Version): Option[(List[Long], List[String], List[String])] = + v match { + case ValidVersion(_, releasePart, preReleasePart, buildPart) + if preReleasePart.nonEmpty && buildPart.nonEmpty => + Some(releasePart, preReleasePart, buildPart) + case _ => None + } +} + +private[dependency] object SnapshotVersion { + def unapply(v: Version): Option[(List[Long], List[String], List[String])] = + v match { + case ValidVersion(_, releasePart, preReleasePart, buildPart) + if preReleasePart.lastOption.contains("SNAPSHOT") => + Some(releasePart, preReleasePart, buildPart) + case _ => None + } +} + +private[dependency] object BuildVersion { + def unapply(v: Version): Option[(List[Long], List[String])] = v match { + case ValidVersion(_, releasePart, Nil, buildPart) if buildPart.nonEmpty => + Some(releasePart, buildPart) + case _ => None + } +} + +private[dependency] object Version { + def apply(text: String): Version = synchronized { + VersionParser + .parse(text) + .fold( + (_, _, _) => InvalidVersion(text), + { case ((a, b, c), _) => ValidVersion(text, a.toList, b.toList, c.toList)} + ) + } + + implicit def versionOrdering: Ordering[Version] = VersionOrdering +} + +private[dependency] object VersionOrdering extends Ordering[Version] { + + private val subParts = "(\\d+)?(\\D+)?".r + + private def parsePart(s: String): Seq[Either[Int, String]] = + try { + subParts + .findAllIn(s) + .matchData + .flatMap { + case Groups(num, str) => + Seq(Option(num).map(_.toInt).map(Left.apply), + Option(str).map(Right.apply)) + } + .flatten + .toList + } catch { + case _: NumberFormatException => List(Right(s)) + } + + private def toOpt(x: Int): Option[Int] = if (x == 0) None else Some(x) + + private def comparePart(a: String, b: String) = { + if (a == b) None + else + (parsePart(a) zip parsePart(b)) map { + case (Left(x), Left(y)) => x compareTo y + case (Left(_), Right(_)) => -1 + case (Right(_), Left(_)) => 1 + case (Right(x), Right(y)) => x compareTo y + } find (0 != _) orElse Some(a compareTo b) + } + + private def compareNumericParts(a: List[Long], b: List[Long]): Option[Int] = + (a, b) match { + case (ah :: at, bh :: bt) => + toOpt(ah compareTo bh) orElse compareNumericParts(at, bt) + case (ah :: at, Nil) => + toOpt(ah compareTo 0L) orElse compareNumericParts(at, Nil) + case (Nil, bh :: bt) => + toOpt(0L compareTo bh) orElse compareNumericParts(Nil, bt) + case (Nil, Nil) => + None + } + + private def compareParts(a: List[String], b: List[String]): Option[Int] = + (a, b) match { + case (ah :: at, bh :: bt) => + comparePart(ah, bh) orElse compareParts(at, bt) + case (_ :: _, Nil) => + Some(1) + case (Nil, _ :: _) => + Some(-1) + case (Nil, Nil) => + None + } + + def compare(x: Version, y: Version): Int = (x, y) match { + case (InvalidVersion(a), InvalidVersion(b)) => + a compareTo b + case (InvalidVersion(_), _) => + -1 + case (_, InvalidVersion(_)) => + 1 + case (ReleaseVersion(r1), ReleaseVersion(r2)) => + compareNumericParts(r1, r2) getOrElse 0 + case (ReleaseVersion(r1), PreReleaseVersion(r2, p2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (ReleaseVersion(r1), PreReleaseBuildVersion(r2, p2, b2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (ReleaseVersion(r1), BuildVersion(r2, b2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (PreReleaseVersion(r1, p1), ReleaseVersion(r2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (PreReleaseVersion(r1, p1), PreReleaseVersion(r2, p2)) => + compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse 0 + case (PreReleaseVersion(r1, p1), PreReleaseBuildVersion(r2, p2, b2)) => + compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse -1 + case (PreReleaseVersion(r1, p1), BuildVersion(r2, b2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (PreReleaseBuildVersion(r1, p1, b1), ReleaseVersion(r2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (PreReleaseBuildVersion(r1, p1, b1), PreReleaseVersion(r2, p2)) => + compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse 1 + case (PreReleaseBuildVersion(r1, p1, b1), + PreReleaseBuildVersion(r2, p2, b2)) => + compareNumericParts(r1, r2) orElse + compareParts(p1, p2) orElse + compareParts(b1, b2) getOrElse + 0 + case (PreReleaseBuildVersion(r1, p1, b1), BuildVersion(r2, b2)) => + compareNumericParts(r1, r2) getOrElse -1 + case (BuildVersion(r1, b1), ReleaseVersion(r2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (BuildVersion(r1, b1), PreReleaseVersion(r2, p2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (BuildVersion(r1, b1), PreReleaseBuildVersion(r2, p2, b2)) => + compareNumericParts(r1, r2) getOrElse 1 + case (BuildVersion(r1, b1), BuildVersion(r2, b2)) => + compareNumericParts(r1, r2) orElse compareParts(b1, b2) getOrElse 0 + } + +} diff --git a/scalalib/src/dependency/versions/VersionParser.scala b/scalalib/src/dependency/versions/VersionParser.scala new file mode 100644 index 00000000..10aebd73 --- /dev/null +++ b/scalalib/src/dependency/versions/VersionParser.scala @@ -0,0 +1,30 @@ +package mill.scalalib.dependency.versions + +import fastparse._, NoWhitespace._ + +private[dependency] object VersionParser { + + private def numberParser[_: P] = + P(CharIn("0-9").rep(1).!.map(_.toLong)) + private def numericPartParser[_: P] = + P(numberParser ~ &(CharIn(".\\-+") | End)).rep(min = 1, sep = ".") + + private def tokenParser[_: P] = + CharPred(c => c != '.' && c != '-' && c != '+').rep(1).! + private def tokenPartParser[_: P] = + tokenParser.rep(sep = CharIn(".\\-")) + + private def firstPartParser[_: P] = + P(CharIn(".\\-") ~ tokenPartParser).? + + private def secondPartParser[_: P] = + P("+" ~ tokenPartParser).? + + private def versionParser[_: P] = + P(numericPartParser ~ firstPartParser ~ secondPartParser).map { + case (a, b, c) => (a, b.getOrElse(Seq.empty), c.getOrElse(Seq.empty)) + } + + def parse(text: String): Parsed[(Seq[Long], Seq[String], Seq[String])] = + fastparse.parse(text, versionParser(_)) +} diff --git a/scalalib/src/dependency/versions/VersionsFinder.scala b/scalalib/src/dependency/versions/VersionsFinder.scala new file mode 100644 index 00000000..a831ffc3 --- /dev/null +++ b/scalalib/src/dependency/versions/VersionsFinder.scala @@ -0,0 +1,73 @@ +package mill.scalalib.dependency.versions + +import mill.define.{BaseModule, Task} +import mill.eval.Evaluator +import mill.scalalib.dependency.metadata.MetadataLoaderFactory +import mill.scalalib.{Dep, JavaModule, Lib} +import mill.api.Ctx.{Home, Log} +import mill.util.{Loose, Strict} + +private[dependency] object VersionsFinder { + + def findVersions(ctx: Log with Home, + rootModule: BaseModule): Seq[ModuleDependenciesVersions] = { + val evaluator = + new Evaluator(ctx.home, os.pwd / 'out, os.pwd / 'out, rootModule, ctx.log) + + val javaModules = rootModule.millInternal.modules.collect { + case javaModule: JavaModule => javaModule + } + + val resolvedDependencies = resolveDependencies(evaluator, javaModules) + resolveVersions(resolvedDependencies) + } + + private def resolveDependencies(evaluator: Evaluator, + javaModules: Seq[JavaModule]) = + javaModules.map { javaModule => + val depToDependency = + eval(evaluator, javaModule.resolveCoursierDependency) + val deps = evalOrElse(evaluator, javaModule.ivyDeps, Loose.Agg.empty[Dep]) + + val (dependencies, _) = + Lib.resolveDependenciesMetadata(javaModule.repositories, + depToDependency, + deps) + + (javaModule, dependencies) + } + + private def resolveVersions(resolvedDependencies: Seq[ResolvedDependencies]) = + resolvedDependencies.map { + case (javaModule, dependencies) => + val metadataLoaders = + javaModule.repositories.flatMap(MetadataLoaderFactory(_)) + + val versions = dependencies.map { dependency => + val currentVersion = Version(dependency.version) + val allVersions = + metadataLoaders + .flatMap(_.getVersions(dependency.module)) + .toSet + DependencyVersions(dependency, currentVersion, allVersions) + } + + ModuleDependenciesVersions(javaModule, versions) + } + + private def eval[T](evaluator: Evaluator, e: Task[T]): T = + evaluator.evaluate(Strict.Agg(e)).values match { + case Seq() => throw new NoSuchElementException + case Seq(e: T) => e + } + + private def evalOrElse[T](evaluator: Evaluator, + e: Task[T], + default: => T): T = + evaluator.evaluate(Strict.Agg(e)).values match { + case Seq() => default + case Seq(e: T) => e + } + + private type ResolvedDependencies = (JavaModule, Seq[coursier.Dependency]) +} diff --git a/scalalib/src/mill/scalalib/Dep.scala b/scalalib/src/mill/scalalib/Dep.scala deleted file mode 100644 index 714fa21e..00000000 --- a/scalalib/src/mill/scalalib/Dep.scala +++ /dev/null @@ -1,121 +0,0 @@ -package mill.scalalib -import mill.util.JsonFormatters._ -import upickle.default.{macroRW, ReadWriter => RW} - -import CrossVersion._ - -case class Dep(dep: coursier.Dependency, cross: CrossVersion, force: Boolean) { - import mill.scalalib.api.Util.isDotty - - def artifactName(binaryVersion: String, fullVersion: String, platformSuffix: String) = { - val suffix = cross.suffixString(binaryVersion, fullVersion, platformSuffix) - dep.module.name + suffix - } - def configure(attributes: coursier.Attributes): Dep = copy(dep = dep.copy(attributes = attributes)) - def forceVersion(): Dep = copy(force = true) - def exclude(exclusions: (String, String)*) = copy(dep = dep.copy(exclusions = dep.exclusions ++ exclusions)) - def excludeOrg(organizations: String*): Dep = exclude(organizations.map(_ -> "*"): _*) - def excludeName(names: String*): Dep = exclude(names.map("*" -> _): _*) - def toDependency(binaryVersion: String, fullVersion: String, platformSuffix: String) = - dep.copy(module = dep.module.copy(name = artifactName(binaryVersion, fullVersion, platformSuffix))) - def withConfiguration(configuration: String): Dep = copy(dep = dep.copy(configuration = configuration)) - - /** - * If scalaVersion is a Dotty version, replace the cross-version suffix - * by the Scala 2.x version that the Dotty version is retro-compatible with, - * otherwise do nothing. - * - * This setting is useful when your build contains dependencies that have only - * been published with Scala 2.x, if you have: - * {{{ - * def ivyDeps = Agg(ivy"a::b:c") - * }}} - * you can replace it by: - * {{{ - * def ivyDeps = Agg(ivy"a::b:c".withDottyCompat(scalaVersion())) - * }}} - * This will have no effect when compiling with Scala 2.x, but when compiling - * with Dotty this will change the cross-version to a Scala 2.x one. This - * works because Dotty is currently retro-compatible with Scala 2.x. - */ - def withDottyCompat(scalaVersion: String): Dep = - cross match { - case cross: Binary if isDotty(scalaVersion) => - copy(cross = Constant(value = "_2.12", platformed = cross.platformed)) - case _ => - this - } -} - -object Dep { - - val DefaultConfiguration = "default(compile)" - - implicit def parse(signature: String): Dep = { - val parts = signature.split(';') - val module = parts.head - val attributes = parts.tail.foldLeft(coursier.Attributes()) { (as, s) => - s.split('=') match { - case Array("classifier", v) => as.copy(classifier = v) - case Array(k, v) => throw new Exception(s"Unrecognized attribute: [$s]") - case _ => throw new Exception(s"Unable to parse attribute specifier: [$s]") - } - } - (module.split(':') match { - case Array(a, b, c) => Dep(a, b, c, cross = empty(platformed = false)) - case Array(a, b, "", c) => Dep(a, b, c, cross = empty(platformed = true)) - case Array(a, "", b, c) => Dep(a, b, c, cross = Binary(platformed = false)) - case Array(a, "", b, "", c) => Dep(a, b, c, cross = Binary(platformed = true)) - case Array(a, "", "", b, c) => Dep(a, b, c, cross = Full(platformed = false)) - case Array(a, "", "", b, "", c) => Dep(a, b, c, cross = Full(platformed = true)) - case _ => throw new Exception(s"Unable to parse signature: [$signature]") - }).configure(attributes = attributes) - } - def apply(org: String, name: String, version: String, cross: CrossVersion, force: Boolean = false): Dep = { - apply(coursier.Dependency(coursier.Module(org, name), version, DefaultConfiguration), cross, force) - } - implicit def rw: RW[Dep] = macroRW -} - -sealed trait CrossVersion { - /** If true, the cross-version suffix should start with a platform suffix if it exists */ - def platformed: Boolean - - def isBinary: Boolean = - this.isInstanceOf[Binary] - def isConstant: Boolean = - this.isInstanceOf[Constant] - def isFull: Boolean = - this.isInstanceOf[Full] - - /** The string that should be appended to the module name to get the artifact name */ - def suffixString(binaryVersion: String, fullVersion: String, platformSuffix: String): String = { - val firstSuffix = if (platformed) platformSuffix else "" - this match { - case cross: Constant => - s"${firstSuffix}${cross.value}" - case cross: Binary => - s"${firstSuffix}_${binaryVersion}" - case cross: Full => - s"${firstSuffix}_${fullVersion}" - } - } -} -object CrossVersion { - case class Constant(value: String, platformed: Boolean) extends CrossVersion - object Constant { - implicit def rw: RW[Constant] = macroRW - } - case class Binary(platformed: Boolean) extends CrossVersion - object Binary { - implicit def rw: RW[Binary] = macroRW - } - case class Full(platformed: Boolean) extends CrossVersion - object Full { - implicit def rw: RW[Full] = macroRW - } - - def empty(platformed: Boolean) = Constant(value = "", platformed) - - implicit def rw: RW[CrossVersion] = RW.merge(Constant.rw, Binary.rw, Full.rw) -} diff --git a/scalalib/src/mill/scalalib/Dependency.scala b/scalalib/src/mill/scalalib/Dependency.scala deleted file mode 100644 index 0c589663..00000000 --- a/scalalib/src/mill/scalalib/Dependency.scala +++ /dev/null @@ -1,22 +0,0 @@ -package mill.scalalib - -import mill.T -import mill.define.{Discover, ExternalModule} -import mill.eval.Evaluator -import mill.main.EvaluatorScopt -import mill.scalalib.dependency.DependencyUpdatesImpl - -object Dependency extends ExternalModule { - - def updates(ev: Evaluator, allowPreRelease: Boolean = false) = - T.command { - DependencyUpdatesImpl(implicitly, - ev.rootModule, - ev.rootModule.millDiscover, - allowPreRelease) - } - - implicit def millScoptEvaluatorReads[T]: EvaluatorScopt[T] = - new mill.main.EvaluatorScopt[T]() - lazy val millDiscover: Discover[Dependency.this.type] = Discover[this.type] -} diff --git a/scalalib/src/mill/scalalib/GenIdeaImpl.scala b/scalalib/src/mill/scalalib/GenIdeaImpl.scala deleted file mode 100644 index 2d76d804..00000000 --- a/scalalib/src/mill/scalalib/GenIdeaImpl.scala +++ /dev/null @@ -1,474 +0,0 @@ -package mill.scalalib - -import ammonite.runtime.SpecialClassLoader -import coursier.{Cache, CoursierPaths, Repository} -import mill.define._ -import mill.eval.{Evaluator, PathRef, Result} -import mill.api.Ctx.{Home, Log} -import mill.util.Strict.Agg -import mill.util.{Loose, Strict} -import mill.{T, scalalib} - -import scala.util.Try - - -object GenIdea extends ExternalModule { - - def idea(ev: Evaluator) = T.command{ - mill.scalalib.GenIdeaImpl( - implicitly, - ev.rootModule, - ev.rootModule.millDiscover - ) - } - - implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]() - lazy val millDiscover = Discover[this.type] -} - -object GenIdeaImpl { - - def apply(ctx: Log with Home, - rootModule: BaseModule, - discover: Discover[_]): Unit = { - val pp = new scala.xml.PrettyPrinter(999, 4) - - val jdkInfo = extractCurrentJdk(os.pwd / ".idea" / "misc.xml").getOrElse(("JDK_1_8", "1.8 (1)")) - - os.remove.all(os.pwd/".idea"/"libraries") - os.remove.all(os.pwd/".idea"/"scala_compiler.xml") - os.remove.all(os.pwd/".idea_modules") - - - val evaluator = new Evaluator(ctx.home, os.pwd / 'out, os.pwd / 'out, rootModule, ctx.log) - - for((relPath, xml) <- xmlFileLayout(evaluator, rootModule, jdkInfo)){ - os.write.over(os.pwd/relPath, pp.format(xml)) - } - } - - def extractCurrentJdk(ideaPath: os.Path): Option[(String,String)] = { - import scala.xml.XML - Try { - val xml = XML.loadFile(ideaPath.toString) - (xml \\ "component") - .filter(x => x.attribute("project-jdk-type").map(_.text).contains("JavaSDK")) - .map { n => (n.attribute("languageLevel"), n.attribute("project-jdk-name")) } - .collectFirst{ case (Some(lang), Some(jdk)) => (lang.text, jdk.text) } - }.getOrElse(None) - } - - def xmlFileLayout(evaluator: Evaluator, - rootModule: mill.Module, - jdkInfo: (String,String), - fetchMillModules: Boolean = true): Seq[(os.RelPath, scala.xml.Node)] = { - - val modules = rootModule.millInternal.segmentsToModules.values - .collect{ case x: scalalib.JavaModule => (x.millModuleSegments, x)} - .toSeq - - val buildLibraryPaths = - if (!fetchMillModules) Nil - else sys.props.get("MILL_BUILD_LIBRARIES") match { - case Some(found) => found.split(',').map(os.Path(_)).distinct.toList - case None => - val repos = modules.foldLeft(Set.empty[Repository]) { _ ++ _._2.repositories } - val artifactNames = Seq("main-moduledefs", "main-core", "scalalib", "scalajslib") - val Result.Success(res) = scalalib.Lib.resolveDependencies( - repos.toList, - Lib.depToDependency(_, "2.12.4", ""), - for(name <- artifactNames) - yield ivy"com.lihaoyi::mill-$name:${sys.props("MILL_VERSION")}" - ) - res.items.toList.map(_.path) - } - - val buildDepsPaths = Try(evaluator - .rootModule - .getClass - .getClassLoader - .asInstanceOf[SpecialClassLoader] - ).map { - _.allJars - .map(url => os.Path(url.getFile)) - .filter(_.toIO.exists) - }.getOrElse(Seq()) - - val resolved = for((path, mod) <- modules) yield { - val scalaLibraryIvyDeps = mod match{ - case x: ScalaModule => x.scalaLibraryIvyDeps - case _ => T.task{Nil} - } - val allIvyDeps = T.task{mod.transitiveIvyDeps() ++ scalaLibraryIvyDeps() ++ mod.compileIvyDeps()} - val externalDependencies = T.task{ - mod.resolveDeps(allIvyDeps)() ++ - Task.traverse(mod.transitiveModuleDeps)(_.unmanagedClasspath)().flatten - } - - val externalSources = T.task{ - mod.resolveDeps(allIvyDeps, sources = true)() - } - - val (scalacPluginsIvyDeps, scalacOptions) = mod match{ - case mod: ScalaModule => T.task{mod.scalacPluginIvyDeps()} -> T.task{mod.scalacOptions()} - case _ => T.task(Loose.Agg[Dep]()) -> T.task(Seq()) - } - val scalacPluginDependencies = T.task{ - mod.resolveDeps(scalacPluginsIvyDeps)() - } - - val resolvedCp: Loose.Agg[PathRef] = evalOrElse(evaluator, externalDependencies, Loose.Agg.empty) - val resolvedSrcs: Loose.Agg[PathRef] = evalOrElse(evaluator, externalSources, Loose.Agg.empty) - val resolvedSp: Loose.Agg[PathRef] = evalOrElse(evaluator, scalacPluginDependencies, Loose.Agg.empty) - val scalacOpts: Seq[String] = evalOrElse(evaluator, scalacOptions, Seq()) - - ( - path, - resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path), - mod, - resolvedSp.map(_.path).filter(_.ext == "jar"), - scalacOpts - ) - } - val moduleLabels = modules.map(_.swap).toMap - - val allResolved = resolved.flatMap(_._2) ++ buildLibraryPaths ++ buildDepsPaths - - val commonPrefix = - if (allResolved.isEmpty) 0 - else { - val minResolvedLength = allResolved.map(_.segmentCount).min - allResolved.map(_.segments.take(minResolvedLength).toList) - .transpose - .takeWhile(_.distinct.length == 1) - .length - } - - // only resort to full long path names if the jar name is a duplicate - val pathShortLibNameDuplicate = allResolved - .distinct - .map{p => p.last -> p} - .groupBy(_._1) - .filter(_._2.size > 1) - .keySet - - val pathToLibName = allResolved - .map{p => - if (pathShortLibNameDuplicate(p.last)) - (p, p.segments.drop(commonPrefix).mkString("_")) - else - (p, p.last) - } - .toMap - - sealed trait ResolvedLibrary { def path : os.Path } - case class CoursierResolved(path : os.Path, pom : os.Path, sources : Option[os.Path]) - extends ResolvedLibrary - case class OtherResolved(path : os.Path) extends ResolvedLibrary - - // Tries to group jars with their poms and sources. - def toResolvedJar(path : os.Path) : Option[ResolvedLibrary] = { - val inCoursierCache = path.startsWith(os.Path(CoursierPaths.cacheDirectory())) - val isSource = path.last.endsWith("sources.jar") - val isPom = path.ext == "pom" - if (inCoursierCache && (isSource || isPom)) { - // Remove sources and pom as they'll be recovered from the jar path - None - } else if (inCoursierCache && path.ext == "jar") { - val withoutExt = path.last.dropRight(path.ext.length + 1) - val pom = path / os.up / s"$withoutExt.pom" - val sources = Some(path / os.up / s"$withoutExt-sources.jar") - .filter(_.toIO.exists()) - Some(CoursierResolved(path, pom, sources)) - } else Some(OtherResolved(path)) - } - - // Hack so that Intellij does not complain about unresolved magic - // imports in build.sc when in fact they are resolved - def sbtLibraryNameFromPom(pom : os.Path) : String = { - val xml = scala.xml.XML.loadFile(pom.toIO) - - val groupId = (xml \ "groupId").text - val artifactId = (xml \ "artifactId").text - val version = (xml \ "version").text - - // The scala version here is non incidental - s"SBT: $groupId:$artifactId:$version:jar" - } - - def libraryName(resolvedJar: ResolvedLibrary) : String = resolvedJar match { - case CoursierResolved(path, pom, _) if buildDepsPaths.contains(path) => - sbtLibraryNameFromPom(pom) - case CoursierResolved(path, _, _) => - pathToLibName(path) - case OtherResolved(path) => - pathToLibName(path) - } - - def resolvedLibraries(resolved : Seq[os.Path]) : Seq[ResolvedLibrary] = resolved - .map(toResolvedJar) - .collect { case Some(r) => r} - - val compilerSettings = resolved - .foldLeft(Map[(Loose.Agg[os.Path], Seq[String]), Vector[JavaModule]]()) { - (r, q) => - val key = (q._4, q._5) - r + (key -> (r.getOrElse(key, Vector()) :+ q._3)) - } - - val allBuildLibraries : Set[ResolvedLibrary] = - resolvedLibraries(buildLibraryPaths ++ buildDepsPaths).toSet - - val fixedFiles = Seq( - Tuple2(os.rel/".idea"/"misc.xml", miscXmlTemplate(jdkInfo)), - Tuple2(os.rel/".idea"/"scala_settings.xml", scalaSettingsTemplate()), - Tuple2( - os.rel/".idea"/"modules.xml", - allModulesXmlTemplate( - modules - .filter(!_._2.skipIdea) - .map { case (path, mod) => moduleName(path) } - ) - ), - Tuple2( - os.rel/".idea_modules"/"mill-build.iml", - rootXmlTemplate( - for(lib <- allBuildLibraries) - yield libraryName(lib) - ) - ), - Tuple2( - os.rel/".idea"/"scala_compiler.xml", - scalaCompilerTemplate(compilerSettings) - ) - ) - - val libraries = resolvedLibraries(allResolved).map{ resolved => - import resolved.path - val url = "jar://" + path + "!/" - val name = libraryName(resolved) - val sources = resolved match { - case CoursierResolved(_, _, s) => s.map(p => "jar://" + p + "!/") - case OtherResolved(_) => None - } - Tuple2(os.rel/".idea"/'libraries/s"$name.xml", libraryXmlTemplate(name, url, sources)) - } - - val moduleFiles = resolved.map{ case (path, resolvedDeps, mod, _, _) => - val Seq( - resourcesPathRefs: Seq[PathRef], - sourcesPathRef: Seq[PathRef], - generatedSourcePathRefs: Seq[PathRef], - allSourcesPathRefs: Seq[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.outPath, - mod.compile.ctx.segments - ) - val scalaVersionOpt = mod match { - case x: ScalaModule => Some(evaluator.evaluate(Agg(x.scalaVersion)).values.head.asInstanceOf[String]) - case _ => None - } - val generatedSourceOutPath = Evaluator.resolveDestPaths( - evaluator.outPath, - mod.generatedSources.ctx.segments - ) - - val isTest = mod.isInstanceOf[TestModule] - - val elem = moduleXmlTemplate( - mod.intellijModulePath, - scalaVersionOpt, - Strict.Agg.from(resourcesPathRefs.map(_.path)), - Strict.Agg.from(normalSourcePaths), - Strict.Agg.from(generatedSourcePaths), - paths.out, - generatedSourceOutPath.dest, - Strict.Agg.from(resolvedDeps.map(pathToLibName)), - Strict.Agg.from(mod.moduleDeps.map{ m => moduleName(moduleLabels(m))}.distinct), - isTest - ) - Tuple2(os.rel/".idea_modules"/s"${moduleName(path)}.iml", elem) - } - - fixedFiles ++ libraries ++ moduleFiles - } - - def evalOrElse[T](evaluator: Evaluator, e: Task[T], default: => T): T = { - evaluator.evaluate(Agg(e)).values match { - case Seq() => default - case Seq(e: T) => e - } - } - - def relify(p: os.Path) = { - val r = p.relativeTo(os.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 scalaSettingsTemplate() = { - - - - - - } - def miscXmlTemplate(jdkInfo: (String,String)) = { - - - - - - } - - def allModulesXmlTemplate(selectors: Seq[String]) = { - - - - - { - for(selector <- selectors) - yield { - val filepath = "$PROJECT_DIR$/.idea_modules/" + selector + ".iml" - val fileurl = "file://" + filepath - - } - } - - - - } - def rootXmlTemplate(libNames: Strict.Agg[String]) = { - - - - - - - - - - - { - for(name <- libNames.toSeq.sorted) - yield - } - - - } - def libraryXmlTemplate(name: String, url: String, sources: Option[String]) = { - - - - - - { if (sources.isDefined) { - - - - } - } - - - } - def moduleXmlTemplate(basePath: os.Path, - scalaVersionOpt: Option[String], - resourcePaths: Strict.Agg[os.Path], - normalSourcePaths: Strict.Agg[os.Path], - generatedSourcePaths: Strict.Agg[os.Path], - compileOutputPath: os.Path, - generatedSourceOutputPath: os.Path, - libNames: Strict.Agg[String], - depNames: Strict.Agg[String], - isTest: Boolean - ) = { - - - { - val outputUrl = "file://$MODULE_DIR$/" + relify(compileOutputPath) + "/dest/classes" - if (isTest) - - else - - } - - - - { - for (normalSourcePath <- normalSourcePaths.toSeq.sorted) - yield - - } - { - for (generatedSourcePath <- generatedSourcePaths.toSeq.sorted) - yield - - } - { - val resourceType = if (isTest) "java-test-resource" else "java-resource" - for (resourcePath <- resourcePaths.toSeq.sorted) - yield - - } - - - - - { - for(scalaVersion <- scalaVersionOpt.toSeq) - yield - } - - { - for(name <- libNames.toSeq.sorted) - yield - - } - { - for(depName <- depNames.toSeq.sorted) - yield - } - - - } - def scalaCompilerTemplate(settings: Map[(Loose.Agg[os.Path], Seq[String]), Seq[JavaModule]]) = { - - - - { - for((((plugins, params), mods), i) <- settings.toSeq.zip(1 to settings.size)) - yield - moduleName(m.millModuleSegments)).mkString(",")}> - - { - for(param <- params) - yield - } - - - { - for(plugin <- plugins.toSeq) - yield - } - - - } - - - } -} diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala deleted file mode 100644 index 78be8893..00000000 --- a/scalalib/src/mill/scalalib/JavaModule.scala +++ /dev/null @@ -1,608 +0,0 @@ -package mill -package scalalib - -import coursier.Repository -import mill.define.Task -import mill.define.TaskModule -import mill.eval.{PathRef, Result} -import mill.modules.{Assembly, Jvm} -import mill.modules.Jvm.{createAssembly, createJar} -import Lib._ -import mill.scalalib.publish.{Artifact, Scope} -import mill.util.Loose.Agg - -/** - * Core configuration required to compile a single Scala compilation target - */ -trait JavaModule extends mill.Module with TaskModule { outer => - def zincWorker: ZincWorkerModule = mill.scalalib.ZincWorkerModule - - trait Tests extends TestModule{ - override def moduleDeps = Seq(outer) - override def repositories = outer.repositories - override def javacOptions = outer.javacOptions - override def zincWorker = outer.zincWorker - } - def defaultCommandName() = "run" - - def resolvePublishDependency: Task[Dep => publish.Dependency] = T.task{ - Artifact.fromDepJava(_: Dep) - } - def resolveCoursierDependency: Task[Dep => coursier.Dependency] = T.task{ - Lib.depToDependencyJava(_: Dep) - } - - /** - * Allows you to specify an explicit main class to use for the `run` command. - * If none is specified, the classpath is searched for an appropriate main - * class to use if one exists - */ - def mainClass: T[Option[String]] = None - - def finalMainClassOpt: T[Either[String, String]] = T{ - mainClass() match{ - case Some(m) => Right(m) - case None => - zincWorker.worker().discoverMainClasses(compile())match { - case Seq() => Left("No main class specified or found") - case Seq(main) => Right(main) - case mains => - Left( - s"Multiple main classes found (${mains.mkString(",")}) " + - "please explicitly specify which one to use by overriding mainClass" - ) - } - } - } - - def finalMainClass: T[String] = T{ - finalMainClassOpt() match { - case Right(main) => Result.Success(main) - case Left(msg) => Result.Failure(msg) - } - } - - /** - * Any ivy dependencies you want to add to this Module, in the format - * ivy"org::name:version" for Scala dependencies or ivy"org:name:version" - * for Java dependencies - */ - def ivyDeps = T{ Agg.empty[Dep] } - - /** - * Same as `ivyDeps`, but only present at compile time. Useful for e.g. - * macro-related dependencies like `scala-reflect` that doesn't need to be - * present at runtime - */ - def compileIvyDeps = T{ Agg.empty[Dep] } - /** - * Same as `ivyDeps`, but only present at runtime. Useful for e.g. - * selecting different versions of a dependency to use at runtime after your - * code has already been compiled - */ - def runIvyDeps = T{ Agg.empty[Dep] } - - /** - * Options to pass to the java compiler - */ - def javacOptions = T{ Seq.empty[String] } - - /** The direct dependencies of this module */ - def moduleDeps = Seq.empty[JavaModule] - - /** The direct and indirect dependencies of this module */ - def recursiveModuleDeps: Seq[JavaModule] = { - moduleDeps.flatMap(_.transitiveModuleDeps).distinct - } - - /** Like `recursiveModuleDeps` but also include the module itself */ - def transitiveModuleDeps: Seq[JavaModule] = { - Seq(this) ++ recursiveModuleDeps - } - - /** - * Additional jars, classfiles or resources to add to the classpath directly - * from disk rather than being downloaded from Maven Central or other package - * repositories - */ - def unmanagedClasspath = T{ Agg.empty[PathRef] } - - /** - * The transitive ivy dependencies of this module and all it's upstream modules - */ - def transitiveIvyDeps: T[Agg[Dep]] = T{ - ivyDeps() ++ Task.traverse(moduleDeps)(_.transitiveIvyDeps)().flatten - } - - /** - * The upstream compilation output of all this module's upstream modules - */ - def upstreamCompileOutput = T{ - Task.traverse(recursiveModuleDeps)(_.compile) - } - - /** - * The transitive version of `localClasspath` - */ - def transitiveLocalClasspath: T[Agg[PathRef]] = T{ - Task.traverse(moduleDeps)(m => - T.task{m.localClasspath() ++ m.transitiveLocalClasspath()} - )().flatten - } - - def mapDependencies = T.task{ d: coursier.Dependency => d } - - def resolveDeps(deps: Task[Agg[Dep]], sources: Boolean = false) = T.task{ - resolveDependencies( - repositories, - resolveCoursierDependency().apply(_), - deps(), - sources, - mapDependencies = Some(mapDependencies()) - ) - } - - - def repositories: Seq[Repository] = zincWorker.repositories - - /** - * What platform suffix to use for publishing, e.g. `_sjs` for Scala.js - * projects - */ - def platformSuffix = T{ "" } - - private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r - - /** - * What shell script to use to launch the executable generated by `assembly`. - * Defaults to a generic "universal" launcher that should work for Windows, - * OS-X and Linux - */ - def prependShellScript: T[String] = T{ - finalMainClassOpt().toOption match{ - case None => "" - case Some(cls) => - val isWin = scala.util.Properties.isWin - mill.modules.Jvm.launcherUniversalScript( - cls, - Agg("$0"), Agg("%~dpnx0"), - forkArgs() - ) - } - } - - def assemblyRules: Seq[Assembly.Rule] = Assembly.defaultRules - - /** - * The folders where the source files for this module live - */ - def sources = T.sources{ millSourcePath / 'src } - /** - * The folders where the resource files for this module live - */ - def resources = T.sources{ millSourcePath / 'resources } - /** - * Folders containing source files that are generated rather than - * hand-written; these files can be generated in this target itself, - * or can refer to files generated from other targets - */ - def generatedSources = T{ Seq.empty[PathRef] } - - /** - * The folders containing all source files fed into the compiler - */ - def allSources = T{ sources() ++ generatedSources() } - - /** - * All individual source files fed into the compiler - */ - def allSourceFiles = T{ - def isHiddenFile(path: os.Path) = path.last.startsWith(".") - for { - root <- allSources() - if os.exists(root.path) - path <- (if (os.isDir(root.path)) os.walk(root.path) else Seq(root.path)) - if os.isFile(path) && ((path.ext == "scala" || path.ext == "java") && !isHiddenFile(path)) - } yield PathRef(path) - } - - /** - * Compiles the current module to generate compiled classfiles/bytecode - */ - def compile: T[mill.scalalib.api.CompilationResult] = T.persistent{ - zincWorker.worker().compileJava( - upstreamCompileOutput(), - allSourceFiles().map(_.path), - compileClasspath().map(_.path), - javacOptions() - ) - } - - /** - * The output classfiles/resources from this module, excluding upstream - * modules and third-party dependencies - */ - def localClasspath = T{ - resources() ++ Agg(compile().classes) - } - - /** - * All classfiles and resources from upstream modules and dependencies - * necessary to compile this module - */ - def compileClasspath = T{ - transitiveLocalClasspath() ++ - resources() ++ - unmanagedClasspath() ++ - resolveDeps(T.task{compileIvyDeps() ++ transitiveIvyDeps()})() - } - - /** - * All upstream classfiles and resources necessary to build and executable - * assembly, but without this module's contribution - */ - def upstreamAssemblyClasspath = T{ - transitiveLocalClasspath() ++ - unmanagedClasspath() ++ - resolveDeps(T.task{runIvyDeps() ++ transitiveIvyDeps()})() - } - - /** - * All classfiles and resources from upstream modules and dependencies - * necessary to run this module's code after compilation - */ - def runClasspath = T{ - localClasspath() ++ - upstreamAssemblyClasspath() - } - - /** - * Build the assembly for upstream dependencies separate from the current - * classpath - * - * This should allow much faster assembly creation in the common case where - * upstream dependencies do not change - */ - def upstreamAssembly = T{ - createAssembly( - upstreamAssemblyClasspath().map(_.path), - mainClass(), - assemblyRules = assemblyRules - ) - } - - /** - * An executable uber-jar/assembly containing all the resources and compiled - * classfiles from this module and all it's upstream modules and dependencies - */ - def assembly = T{ - createAssembly( - Agg.from(localClasspath().map(_.path)), - finalMainClassOpt().toOption, - prependShellScript(), - Some(upstreamAssembly().path), - assemblyRules - ) - } - - /** - * A jar containing only this module's resources and compiled classfiles, - * without those from upstream modules and dependencies - */ - def jar = T{ - createJar( - localClasspath().map(_.path).filter(os.exists), - mainClass() - ) - } - - /** - * The documentation jar, containing all the Javadoc/Scaladoc HTML files, for - * publishing to Maven Central - */ - def docJar = T[PathRef] { - val outDir = T.ctx().dest - - val javadocDir = outDir / 'javadoc - os.makeDir.all(javadocDir) - - val files = for{ - ref <- allSources() - if os.exists(ref.path) - p <- (if (os.isDir(ref.path)) os.walk(ref.path) else Seq(ref.path)) - if os.isFile(p) && (p.ext == "java") - } yield p.toNIO.toString - - val options = Seq("-d", javadocDir.toNIO.toString) - - if (files.nonEmpty) Jvm.baseInteractiveSubprocess( - commandArgs = Seq( - "javadoc" - ) ++ options ++ - Seq( - "-classpath", - compileClasspath() - .map(_.path) - .filter(_.ext != "pom") - .mkString(java.io.File.pathSeparator) - ) ++ - files.map(_.toString), - envArgs = Map(), - workingDir = T.ctx().dest - ) - - createJar(Agg(javadocDir))(outDir) - } - - /** - * The source jar, containing only source code for publishing to Maven Central - */ - def sourceJar = T { - createJar((allSources() ++ resources()).map(_.path).filter(os.exists)) - } - - /** - * Any command-line parameters you want to pass to the forked JVM under `run`, - * `test` or `repl` - */ - def forkArgs = T{ Seq.empty[String] } - - /** - * Any environment variables you want to pass to the forked JVM under `run`, - * `test` or `repl` - */ - def forkEnv = T{ sys.env.toMap } - - /** - * Builds a command-line "launcher" file that can be used to run this module's - * code, without the Mill process. Useful for deployment & other places where - * you do not want a build tool running - */ - def launcher = T{ - Result.Success( - Jvm.createLauncher( - finalMainClass(), - runClasspath().map(_.path), - forkArgs() - ) - ) - } - - def ivyDepsTree(inverse: Boolean = false) = T.command { - val (flattened, resolution) = Lib.resolveDependenciesMetadata( - repositories, - resolveCoursierDependency().apply(_), - transitiveIvyDeps(), - Some(mapDependencies()) - ) - - println(coursier.util.Print.dependencyTree(flattened, resolution, - printExclusions = false, reverse = inverse)) - - Result.Success() - } - - /** - * Runs this module's code in-process within an isolated classloader. This is - * faster than `run`, but in exchange you have less isolation between runs - * since the code can dirty the parent Mill process and potentially leave it - * in a bad state. - */ - def runLocal(args: String*) = T.command { - Jvm.runLocal( - finalMainClass(), - runClasspath().map(_.path), - args - ) - } - - /** - * Runs this module's code in a subprocess and waits for it to finish - */ - def run(args: String*) = T.command{ - try Result.Success(Jvm.runSubprocess( - finalMainClass(), - runClasspath().map(_.path), - forkArgs(), - forkEnv(), - args, - workingDir = forkWorkingDir() - )) catch { case e: Exception => - Result.Failure("subprocess failed") - } - } - - private[this] def backgroundSetup(dest: os.Path) = { - val token = java.util.UUID.randomUUID().toString - val procId = dest / ".mill-background-process-id" - val procTombstone = dest / ".mill-background-process-tombstone" - // The backgrounded subprocesses poll the procId file, and kill themselves - // when the procId file is deleted. This deletion happens immediately before - // the body of these commands run, but we cannot be sure the subprocess has - // had time to notice. - // - // To make sure we wait for the previous subprocess to - // die, we make the subprocess write a tombstone file out when it kills - // itself due to procId being deleted, and we wait a short time on task-start - // to see if such a tombstone appears. If a tombstone appears, we can be sure - // the subprocess has killed itself, and can continue. If a tombstone doesn't - // appear in a short amount of time, we assume the subprocess exited or was - // killed via some other means, and continue anyway. - val start = System.currentTimeMillis() - while({ - if (os.exists(procTombstone)) { - Thread.sleep(10) - os.remove.all(procTombstone) - true - } else { - Thread.sleep(10) - System.currentTimeMillis() - start < 100 - } - })() - - os.write(procId, token) - os.write(procTombstone, token) - (procId, procTombstone, token) - } - - /** - * Runs this module's code in a background process, until it dies or - * `runBackground` is used again. This lets you continue using Mill while - * the process is running in the background: editing files, compiling, and - * only re-starting the background process when you're ready. - * - * You can also use `-w foo.runBackground` to make Mill watch for changes - * and automatically recompile your code & restart the background process - * when ready. This is useful when working on long-running server processes - * that would otherwise run forever - */ - def runBackground(args: String*) = T.command{ - val (procId, procTombstone, token) = backgroundSetup(T.ctx().dest) - try Result.Success(Jvm.runSubprocess( - "mill.scalalib.backgroundwrapper.BackgroundWrapper", - (runClasspath() ++ zincWorker.backgroundWrapperClasspath()).map(_.path), - forkArgs(), - forkEnv(), - Seq(procId.toString, procTombstone.toString, token, finalMainClass()) ++ args, - workingDir = forkWorkingDir(), - background = true - )) catch { case e: Exception => - Result.Failure("subprocess failed") - } - } - - /** - * Same as `runBackground`, but lets you specify a main class to run - */ - def runMainBackground(mainClass: String, args: String*) = T.command{ - val (procId, procTombstone, token) = backgroundSetup(T.ctx().dest) - try Result.Success(Jvm.runSubprocess( - "mill.scalalib.backgroundwrapper.BackgroundWrapper", - (runClasspath() ++ zincWorker.backgroundWrapperClasspath()).map(_.path), - forkArgs(), - forkEnv(), - Seq(procId.toString, procTombstone.toString, token, mainClass) ++ args, - workingDir = forkWorkingDir(), - background = true - )) catch { case e: Exception => - Result.Failure("subprocess failed") - } - } - - /** - * Same as `runLocal`, but lets you specify a main class to run - */ - def runMainLocal(mainClass: String, args: String*) = T.command { - Jvm.runLocal( - mainClass, - runClasspath().map(_.path), - args - ) - } - - /** - * Same as `run`, but lets you specify a main class to run - */ - def runMain(mainClass: String, args: String*) = T.command{ - try Result.Success(Jvm.runSubprocess( - mainClass, - runClasspath().map(_.path), - forkArgs(), - forkEnv(), - args, - workingDir = forkWorkingDir() - )) catch { case e: Exception => - Result.Failure("subprocess failed") - } - } - - // publish artifact with name "mill_2.12.4" instead of "mill_2.12" - - def artifactName: T[String] = millModuleSegments.parts.mkString("-") - - def artifactId: T[String] = artifactName() - - def intellijModulePath: os.Path = millSourcePath - - def forkWorkingDir = T{ ammonite.ops.pwd } - - /** - * Skip Idea project file generation. - */ - def skipIdea: Boolean = false -} - -trait TestModule extends JavaModule with TaskModule { - override def defaultCommandName() = "test" - /** - * What test frameworks to use. - */ - def testFrameworks: T[Seq[String]] - /** - * Discovers and runs the module's tests in a subprocess, reporting the - * results to the console - */ - def test(args: String*) = T.command{ - val outputPath = T.ctx().dest/"out.json" - - Jvm.runSubprocess( - mainClass = "mill.scalalib.TestRunner", - classPath = zincWorker.scalalibClasspath().map(_.path), - jvmArgs = forkArgs(), - envArgs = forkEnv(), - mainArgs = - Seq(testFrameworks().length.toString) ++ - testFrameworks() ++ - Seq(runClasspath().length.toString) ++ - runClasspath().map(_.path.toString) ++ - Seq(args.length.toString) ++ - args ++ - Seq(outputPath.toString, T.ctx().log.colored.toString, compile().classes.path.toString, T.ctx().home.toString), - workingDir = forkWorkingDir() - ) - - try { - val jsonOutput = ujson.read(outputPath.toIO) - val (doneMsg, results) = upickle.default.read[(String, Seq[TestRunner.Result])](jsonOutput) - TestModule.handleResults(doneMsg, results) - }catch{case e: Throwable => - Result.Failure("Test reporting failed: " + e) - } - - } - - /** - * Discovers and runs the module's tests in-process in an isolated classloader, - * reporting the results to the console - */ - def testLocal(args: String*) = T.command{ - val outputPath = T.ctx().dest/"out.json" - - val (doneMsg, results) = TestRunner.runTests( - TestRunner.frameworks(testFrameworks()), - runClasspath().map(_.path), - Agg(compile().classes.path), - args - ) - - TestModule.handleResults(doneMsg, results) - - } -} - -object TestModule{ - def handleResults(doneMsg: String, results: Seq[TestRunner.Result]) = { - - val badTests = results.filter(x => Set("Error", "Failure").contains(x.status)) - if (badTests.isEmpty) Result.Success((doneMsg, results)) - else { - val suffix = if (badTests.length == 1) "" else " and " + (badTests.length-1) + " more" - - Result.Failure( - badTests.head.fullyQualifiedName + " " + badTests.head.selector + suffix, - Some((doneMsg, results)) - ) - } - } -} - diff --git a/scalalib/src/mill/scalalib/Lib.scala b/scalalib/src/mill/scalalib/Lib.scala deleted file mode 100644 index b8b253bd..00000000 --- a/scalalib/src/mill/scalalib/Lib.scala +++ /dev/null @@ -1,133 +0,0 @@ -package mill -package scalalib - -import java.io.{File, FileInputStream} -import java.lang.annotation.Annotation -import java.lang.reflect.Modifier -import java.util.zip.ZipInputStream -import javax.tools.ToolProvider - -import ammonite.util.Util -import coursier.{Cache, Dependency, Fetch, Repository, Resolution} -import mill.scalalib.api.Util.isDotty -import mill.Agg -import mill.eval.{PathRef, Result} -import mill.modules.Jvm -import mill.api.Ctx -import sbt.testing._ - -import scala.collection.mutable - - -object Lib{ - def depToDependencyJava(dep: Dep, platformSuffix: String = ""): Dependency = { - assert(dep.cross.isConstant, s"Not a Java dependency: $dep") - depToDependency(dep, "", platformSuffix) - } - - def depToDependency(dep: Dep, scalaVersion: String, platformSuffix: String = ""): Dependency = - dep.toDependency( - binaryVersion = mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion), - fullVersion = scalaVersion, - platformSuffix = platformSuffix - ) - - def resolveDependenciesMetadata(repositories: Seq[Repository], - depToDependency: Dep => coursier.Dependency, - deps: TraversableOnce[Dep], - mapDependencies: Option[Dependency => Dependency] = None) = { - val depSeq = deps.toSeq - mill.modules.Jvm.resolveDependenciesMetadata( - repositories, - depSeq.map(depToDependency), - depSeq.filter(_.force).map(depToDependency), - mapDependencies - ) - } - /** - * Resolve dependencies using Coursier. - * - * We do not bother breaking this out into the separate ZincWorker classpath, - * because Coursier is already bundled with mill/Ammonite to support the - * `import $ivy` syntax. - */ - def resolveDependencies(repositories: Seq[Repository], - depToDependency: Dep => coursier.Dependency, - deps: TraversableOnce[Dep], - sources: Boolean = false, - mapDependencies: Option[Dependency => Dependency] = None): Result[Agg[PathRef]] = { - val depSeq = deps.toSeq - mill.modules.Jvm.resolveDependencies( - repositories, - depSeq.map(depToDependency), - depSeq.filter(_.force).map(depToDependency), - sources, - mapDependencies - ) - } - def scalaCompilerIvyDeps(scalaOrganization: String, scalaVersion: String) = - if (mill.scalalib.api.Util.isDotty(scalaVersion)) - Agg(ivy"$scalaOrganization::dotty-compiler:$scalaVersion".forceVersion()) - else - Agg( - ivy"$scalaOrganization:scala-compiler:$scalaVersion".forceVersion(), - ivy"$scalaOrganization:scala-reflect:$scalaVersion".forceVersion() - ) - - def scalaRuntimeIvyDeps(scalaOrganization: String, scalaVersion: String) = Agg[Dep]( - ivy"$scalaOrganization:scala-library:$scalaVersion".forceVersion() - ) - - def listClassFiles(base: os.Path): Iterator[String] = { - if (os.isDir(base)) os.walk(base).toIterator.filter(_.ext == "class").map(_.relativeTo(base).toString) - else { - val zip = new ZipInputStream(new FileInputStream(base.toIO)) - Iterator.continually(zip.getNextEntry).takeWhile(_ != null).map(_.getName).filter(_.endsWith(".class")) - } - } - - def discoverTests(cl: ClassLoader, framework: Framework, classpath: Agg[os.Path]) = { - - val fingerprints = framework.fingerprints() - - val testClasses = classpath.flatMap { base => - // Don't blow up if there are no classfiles representing - // the tests to run Instead just don't run anything - if (!os.exists(base)) Nil - else listClassFiles(base).flatMap { path => - val cls = cl.loadClass(path.stripSuffix(".class").replace('/', '.')) - val publicConstructorCount = - cls.getConstructors.count(c => c.getParameterCount == 0 && Modifier.isPublic(c.getModifiers)) - - if (Modifier.isAbstract(cls.getModifiers) || cls.isInterface || publicConstructorCount > 1) { - None - } else { - (cls.getName.endsWith("$"), publicConstructorCount == 0) match{ - case (true, true) => matchFingerprints(cl, cls, fingerprints, isModule = true) - case (false, false) => matchFingerprints(cl, cls, fingerprints, isModule = false) - case _ => None - } - } - } - } - - testClasses - } - def matchFingerprints(cl: ClassLoader, cls: Class[_], fingerprints: Array[Fingerprint], isModule: Boolean) = { - fingerprints.find { - case f: SubclassFingerprint => - f.isModule == isModule && - cl.loadClass(f.superclassName()).isAssignableFrom(cls) - - case f: AnnotatedFingerprint => - val annotationCls = cl.loadClass(f.annotationName()).asInstanceOf[Class[Annotation]] - f.isModule == isModule && - ( - cls.isAnnotationPresent(annotationCls) || - cls.getDeclaredMethods.exists(_.isAnnotationPresent(annotationCls)) - ) - - }.map { f => (cls, f) } - } - -} diff --git a/scalalib/src/mill/scalalib/MiscModule.scala b/scalalib/src/mill/scalalib/MiscModule.scala deleted file mode 100644 index c6449d6e..00000000 --- a/scalalib/src/mill/scalalib/MiscModule.scala +++ /dev/null @@ -1,101 +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 -object CrossModuleBase{ - def scalaVersionPaths(scalaVersion: String, f: String => os.Path) = { - for(segments <- scalaVersion.split('.').inits.filter(_.nonEmpty)) - yield PathRef(f(segments.mkString("."))) - } -} -trait CrossModuleBase extends ScalaModule { - def crossScalaVersion: String - def scalaVersion = T{ crossScalaVersion } - - override def millSourcePath = super.millSourcePath / ammonite.ops.up - implicit def crossSbtModuleResolver: Resolver[CrossModuleBase] = new Resolver[CrossModuleBase]{ - def resolve[V <: CrossModuleBase](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(",") - ) - ) - } - } -} - -trait CrossScalaModule extends ScalaModule with CrossModuleBase{ outer => - override def sources = T.sources{ - super.sources() ++ - CrossModuleBase.scalaVersionPaths(crossScalaVersion, s => millSourcePath / s"src-$s" ) - } - - trait Tests extends super.Tests { - override def sources = T.sources{ - super.sources() ++ - CrossModuleBase.scalaVersionPaths(crossScalaVersion, s => millSourcePath / s"src-$s" ) - } - } -} - -trait MavenTests extends TestModule{ - override def sources = T.sources( - millSourcePath / 'src / 'test / 'scala, - millSourcePath / 'src / 'test / 'java - ) - override def resources = T.sources{ millSourcePath / 'src / 'test / 'resources } -} -trait MavenModule extends JavaModule{outer => - - override def sources = T.sources( - millSourcePath / 'src / 'main / 'scala, - millSourcePath / 'src / 'main / 'java - ) - override def resources = T.sources{ millSourcePath / 'src / 'main / 'resources } - trait Tests extends super.Tests with MavenTests { - override def millSourcePath = outer.millSourcePath - override def intellijModulePath = outer.millSourcePath / 'src / 'test - } -} - -trait SbtModule extends MavenModule with ScalaModule{ outer => - trait Tests extends super.Tests with MavenTests { - override def millSourcePath = outer.millSourcePath - override def intellijModulePath = outer.millSourcePath / 'src / 'test - } -} - -trait CrossSbtModule extends SbtModule with CrossModuleBase{ outer => - - override def sources = T.sources{ - super.sources() ++ - CrossModuleBase.scalaVersionPaths( - crossScalaVersion, - s => millSourcePath / 'src / 'main / s"scala-$s" - ) - - } - trait Tests extends super.Tests { - override def millSourcePath = outer.millSourcePath - override def sources = T.sources{ - super.sources() ++ - CrossModuleBase.scalaVersionPaths( - crossScalaVersion, - s => millSourcePath / 'src / 'test / s"scala-$s" - ) - } - } -} - - diff --git a/scalalib/src/mill/scalalib/PublishModule.scala b/scalalib/src/mill/scalalib/PublishModule.scala deleted file mode 100644 index 588781f4..00000000 --- a/scalalib/src/mill/scalalib/PublishModule.scala +++ /dev/null @@ -1,124 +0,0 @@ -package mill -package scalalib - -import mill.define.{ExternalModule, Task} -import mill.api.PathRef -import mill.scalalib.publish.{Artifact, SonatypePublisher} - -/** - * Configuration necessary for publishing a Scala module to Maven Central or similar - */ -trait PublishModule extends JavaModule { outer => - import mill.scalalib.publish._ - - override def moduleDeps = Seq.empty[PublishModule] - - def pomSettings: T[PomSettings] - def publishVersion: T[String] - - def publishSelfDependency = T { - Artifact(pomSettings().organization, artifactId(), publishVersion()) - } - - def publishXmlDeps = T.task { - val ivyPomDeps = ivyDeps().map(resolvePublishDependency().apply(_)) - val modulePomDeps = Task.sequence(moduleDeps.map(_.publishSelfDependency))() - ivyPomDeps ++ modulePomDeps.map(Dependency(_, Scope.Compile)) - } - def pom = T { - val pom = Pom(artifactMetadata(), publishXmlDeps(), artifactId(), pomSettings()) - val pomPath = T.ctx().dest / s"${artifactId()}-${publishVersion()}.pom" - os.write.over(pomPath, pom) - PathRef(pomPath) - } - - def ivy = T { - val ivy = Ivy(artifactMetadata(), publishXmlDeps()) - val ivyPath = T.ctx().dest / "ivy.xml" - os.write.over(ivyPath, ivy) - PathRef(ivyPath) - } - - def artifactMetadata: T[Artifact] = T { - Artifact(pomSettings().organization, artifactId(), publishVersion()) - } - - def publishLocal(): define.Command[Unit] = T.command { - LocalPublisher.publish( - jar = jar().path, - sourcesJar = sourceJar().path, - docJar = docJar().path, - pom = pom().path, - ivy = ivy().path, - artifact = artifactMetadata() - ) - } - - def sonatypeUri: String = "https://oss.sonatype.org/service/local" - - def sonatypeSnapshotUri: String = "https://oss.sonatype.org/content/repositories/snapshots" - - def publishArtifacts = T { - val baseName = s"${artifactId()}-${publishVersion()}" - PublishModule.PublishData( - artifactMetadata(), - Seq( - jar() -> s"$baseName.jar", - sourceJar() -> s"$baseName-sources.jar", - docJar() -> s"$baseName-javadoc.jar", - pom() -> s"$baseName.pom" - ) - ) - } - - def publish(sonatypeCreds: String, - gpgPassphrase: String = null, - signed: Boolean = true, - release: Boolean): define.Command[Unit] = T.command { - val PublishModule.PublishData(artifactInfo, artifacts) = publishArtifacts() - new SonatypePublisher( - sonatypeUri, - sonatypeSnapshotUri, - sonatypeCreds, - Option(gpgPassphrase), - signed, - T.ctx().log - ).publish(artifacts.map{case (a, b) => (a.path, b)}, artifactInfo, release) - } -} - -object PublishModule extends ExternalModule { - case class PublishData(meta: Artifact, payload: Seq[(PathRef, String)]) - - object PublishData{ - implicit def jsonify: upickle.default.ReadWriter[PublishData] = upickle.default.macroRW - } - - def publishAll(sonatypeCreds: String, - gpgPassphrase: String = null, - publishArtifacts: mill.main.Tasks[PublishModule.PublishData], - release: Boolean = false, - sonatypeUri: String = "https://oss.sonatype.org/service/local", - sonatypeSnapshotUri: String = "https://oss.sonatype.org/content/repositories/snapshots", - signed: Boolean = true) = T.command { - - val x: Seq[(Seq[(os.Path, String)], Artifact)] = Task.sequence(publishArtifacts.value)().map{ - case PublishModule.PublishData(a, s) => (s.map{case (p, f) => (p.path, f)}, a) - } - new SonatypePublisher( - sonatypeUri, - sonatypeSnapshotUri, - sonatypeCreds, - Option(gpgPassphrase), - signed, - T.ctx().log - ).publishAll( - release, - x:_* - ) - } - - implicit def millScoptTargetReads[T] = new mill.main.Tasks.Scopt[T]() - - lazy val millDiscover: mill.define.Discover[this.type] = mill.define.Discover[this.type] -} diff --git a/scalalib/src/mill/scalalib/ScalaModule.scala b/scalalib/src/mill/scalalib/ScalaModule.scala deleted file mode 100644 index 9d669bf4..00000000 --- a/scalalib/src/mill/scalalib/ScalaModule.scala +++ /dev/null @@ -1,275 +0,0 @@ -package mill -package scalalib - -import coursier.Repository -import mill.define.{Target, Task, TaskModule} -import mill.eval.{PathRef, Result} -import mill.modules.Jvm -import mill.modules.Jvm.createJar -import mill.scalalib.api.Util.isDotty -import Lib._ -import mill.util.Loose.Agg -import mill.api.DummyInputStream - -/** - * Core configuration required to compile a single Scala compilation target - */ -trait ScalaModule extends JavaModule { outer => - trait Tests extends TestModule with ScalaModule{ - override def scalaOrganization = outer.scalaOrganization() - def scalaVersion = outer.scalaVersion() - override def repositories = outer.repositories - override def scalacPluginIvyDeps = outer.scalacPluginIvyDeps - override def scalacOptions = outer.scalacOptions - override def javacOptions = outer.javacOptions - override def zincWorker = outer.zincWorker - override def moduleDeps: Seq[JavaModule] = Seq(outer) - } - - /** - * What Scala organization to use - * @return - */ - def scalaOrganization: T[String] = T { - if (isDotty(scalaVersion())) - "ch.epfl.lamp" - else - "org.scala-lang" - } - - /** - * What version of Scala to use - */ - def scalaVersion: T[String] - - override def mapDependencies = T.task{ d: coursier.Dependency => - val artifacts = - if (isDotty(scalaVersion())) - Set("dotty-library", "dotty-compiler") - else - Set("scala-library", "scala-compiler", "scala-reflect") - if (!artifacts(d.module.name)) d - else d.copy(module = d.module.copy(organization = scalaOrganization()), version = scalaVersion()) - } - - override def resolveCoursierDependency: Task[Dep => coursier.Dependency] = T.task{ - Lib.depToDependency(_: Dep, scalaVersion(), platformSuffix()) - } - - override def resolvePublishDependency: Task[Dep => publish.Dependency] = T.task{ - publish.Artifact.fromDep( - _: Dep, - scalaVersion(), - mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion()), - platformSuffix() - ) - } - - /** - * Allows you to make use of Scala compiler plugins from maven central - */ - def scalacPluginIvyDeps = T{ Agg.empty[Dep] } - - def scalaDocPluginIvyDeps = T{ scalacPluginIvyDeps() } - - /** - * Command-line options to pass to the Scala compiler - */ - def scalacOptions = T{ Seq.empty[String] } - - def scalaDocOptions = T{ scalacOptions() } - - private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r - - def scalaCompilerBridgeSources = T { - val (scalaVersion0, scalaBinaryVersion0) = scalaVersion() match { - case Milestone213(_, _) => ("2.13.0-M2", "2.13.0-M2") - case _ => (scalaVersion(), mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion())) - } - - val (bridgeDep, bridgeName, bridgeVersion) = - if (isDotty(scalaVersion0)) { - val org = scalaOrganization() - val name = "dotty-sbt-bridge" - val version = scalaVersion() - (ivy"$org:$name:$version", name, version) - } else { - val org = "org.scala-sbt" - val name = "compiler-bridge" - val version = Versions.zinc - (ivy"$org::$name:$version", s"${name}_$scalaBinaryVersion0", version) - } - - resolveDependencies( - repositories, - Lib.depToDependency(_, scalaVersion0, platformSuffix()), - Seq(bridgeDep), - sources = true - ).map(deps => - mill.scalalib.api.Util.grepJar(deps.map(_.path), bridgeName, bridgeVersion, sources = true) - ) - } - - /** - * The local classpath of Scala compiler plugins on-disk; you can add - * additional jars here if you have some copiler plugin that isn't present - * on maven central - */ - def scalacPluginClasspath: T[Agg[PathRef]] = T { - resolveDeps(scalacPluginIvyDeps)() - } - - /** - * The ivy coordinates of Scala's own standard library - */ - def scalaDocPluginClasspath: T[Agg[PathRef]] = T { - resolveDeps(scalaDocPluginIvyDeps)() - } - - def scalaLibraryIvyDeps = T{ scalaRuntimeIvyDeps(scalaOrganization(), scalaVersion()) } - - /** - * Classpath of the Scala Compiler & any compiler plugins - */ - def scalaCompilerClasspath: T[Agg[PathRef]] = T{ - resolveDeps( - T.task{ - scalaCompilerIvyDeps(scalaOrganization(), scalaVersion()) ++ - scalaRuntimeIvyDeps(scalaOrganization(), scalaVersion()) - } - )() - } - override def compileClasspath = T{ - transitiveLocalClasspath() ++ - resources() ++ - unmanagedClasspath() ++ - resolveDeps(T.task{compileIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps()})() - } - - override def upstreamAssemblyClasspath = T{ - transitiveLocalClasspath() ++ - unmanagedClasspath() ++ - resolveDeps(T.task{runIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps()})() - } - - override def compile: T[mill.scalalib.api.CompilationResult] = T.persistent{ - zincWorker.worker().compileMixed( - upstreamCompileOutput(), - allSourceFiles().map(_.path), - compileClasspath().map(_.path), - javacOptions(), - scalaVersion(), - scalacOptions(), - scalaCompilerBridgeSources(), - scalaCompilerClasspath().map(_.path), - scalacPluginClasspath().map(_.path), - ) - } - - override def docJar = T { - val outDir = T.ctx().dest - - val javadocDir = outDir / 'javadoc - os.makeDir.all(javadocDir) - - val files = allSourceFiles().map(_.path.toString) - - val pluginOptions = scalaDocPluginClasspath().map(pluginPathRef => s"-Xplugin:${pluginPathRef.path}") - val compileCp = compileClasspath().filter(_.path.ext != "pom").map(_.path) - val options = Seq( - "-d", javadocDir.toNIO.toString, - "-classpath", compileCp.mkString(":") - ) ++ - pluginOptions ++ - scalaDocOptions() - - if (files.isEmpty) Result.Success(createJar(Agg(javadocDir))(outDir)) - else { - zincWorker.worker().docJar( - scalaVersion(), - scalaCompilerBridgeSources(), - scalaCompilerClasspath().map(_.path), - scalacPluginClasspath().map(_.path), - files ++ options - ) match{ - case true => Result.Success(createJar(Agg(javadocDir))(outDir)) - case false => Result.Failure("docJar generation failed") - } - } - } - - /** - * Opens up a Scala console with your module and all dependencies present, - * for you to test and operate your code interactively - */ - def console() = T.command{ - if (T.ctx().log.inStream == DummyInputStream){ - Result.Failure("repl needs to be run with the -i/--interactive flag") - }else{ - Jvm.runSubprocess( - mainClass = - if (isDotty(scalaVersion())) - "dotty.tools.repl.Main" - else - "scala.tools.nsc.MainGenericRunner", - classPath = runClasspath().map(_.path) ++ scalaCompilerClasspath().map(_.path), - mainArgs = Seq("-usejavacp"), - workingDir = os.pwd - ) - Result.Success() - } - } - - /** - * Dependencies that are necessary to run the Ammonite Scala REPL - */ - def ammoniteReplClasspath = T{ - localClasspath() ++ - transitiveLocalClasspath() ++ - unmanagedClasspath() ++ - resolveDeps(T.task{ - runIvyDeps() ++ scalaLibraryIvyDeps() ++ transitiveIvyDeps() ++ - Agg(ivy"com.lihaoyi:::ammonite:${Versions.ammonite}") - })() - } - - /** - * Opens up an Ammonite Scala REPL with your module and all dependencies present, - * for you to test and operate your code interactively - */ - def repl(replOptions: String*) = T.command{ - if (T.ctx().log.inStream == DummyInputStream){ - Result.Failure("repl needs to be run with the -i/--interactive flag") - }else{ - Jvm.runSubprocess( - mainClass = "ammonite.Main", - classPath = ammoniteReplClasspath().map(_.path), - mainArgs = replOptions, - workingDir = os.pwd - ) - Result.Success() - } - - } - - /** - * Whether to publish artifacts with name "mill_2.12.4" instead of "mill_2.12" - */ - def crossFullScalaVersion: T[Boolean] = false - - /** - * What Scala version string to use when publishing - */ - def artifactScalaVersion: T[String] = T { - if (crossFullScalaVersion()) scalaVersion() - else mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion()) - } - - /** - * The suffix appended to the artifact IDs during publishing - */ - def artifactSuffix: T[String] = s"_${artifactScalaVersion()}" - - override def artifactId: T[String] = artifactName() + artifactSuffix() - -} diff --git a/scalalib/src/mill/scalalib/TestRunner.scala b/scalalib/src/mill/scalalib/TestRunner.scala deleted file mode 100644 index 42e65d63..00000000 --- a/scalalib/src/mill/scalalib/TestRunner.scala +++ /dev/null @@ -1,153 +0,0 @@ -package mill.scalalib -import ammonite.util.Colors -import mill.Agg -import mill.modules.Jvm -import mill.scalalib.Lib.discoverTests -import mill.util.{Ctx, PrintLogger} -import mill.util.JsonFormatters._ -import sbt.testing._ - -import scala.collection.mutable -object TestRunner { - - - def main(args: Array[String]): Unit = { - try{ - var i = 0 - def readArray() = { - val count = args(i).toInt - val slice = args.slice(i + 1, i + count + 1) - i = i + count + 1 - slice - } - val frameworks = readArray() - val classpath = readArray() - val arguments = readArray() - val outputPath = args(i + 0) - val colored = args(i + 1) - val testCp = args(i + 2) - val homeStr = args(i + 3) - val ctx = new Ctx.Log with Ctx.Home { - val log = PrintLogger( - colored == "true", - true, - if(colored == "true") Colors.Default - else Colors.BlackWhite, - System.out, - System.err, - System.err, - System.in, - debugEnabled = false - ) - val home = os.Path(homeStr) - } - val result = runTests( - frameworkInstances = TestRunner.frameworks(frameworks), - entireClasspath = Agg.from(classpath.map(os.Path(_))), - testClassfilePath = Agg(os.Path(testCp)), - args = arguments - )(ctx) - - // Clear interrupted state in case some badly-behaved test suite - // dirtied the thread-interrupted flag and forgot to clean up. Otherwise - // that flag causes writing the results to disk to fail - Thread.interrupted() - ammonite.ops.write(os.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 runTests(frameworkInstances: ClassLoader => Seq[sbt.testing.Framework], - entireClasspath: Agg[os.Path], - testClassfilePath: Agg[os.Path], - args: Seq[String]) - (implicit ctx: Ctx.Log with Ctx.Home): (String, Seq[mill.scalalib.TestRunner.Result]) = { - //Leave the context class loader set and open so that shutdown hooks can access it - Jvm.inprocess(entireClasspath, classLoaderOverrideSbtTesting = true, isolated = true, closeContextClassLoaderWhenDone = false, cl => { - val frameworks = frameworkInstances(cl) - - val events = mutable.Buffer.empty[Event] - - val doneMessages = frameworks.map{ framework => - val runner = framework.runner(args.toArray, Array[String](), cl) - - val testClasses = discoverTests(cl, framework, testClassfilePath) - - val tasks = runner.tasks( - for ((cls, fingerprint) <- testClasses.toArray) - yield new TaskDef(cls.getName.stripSuffix("$"), fingerprint, true, Array(new SuiteSelector)) - ) - - val taskQueue = tasks.to[mutable.Queue] - while (taskQueue.nonEmpty){ - val next = taskQueue.dequeue().execute( - new EventHandler { - def handle(event: Event) = events.append(event) - }, - Array( - new Logger { - def debug(msg: String) = ctx.log.outputStream.println(msg) - - def error(msg: String) = ctx.log.outputStream.println(msg) - - def ansiCodesSupported() = true - - def warn(msg: String) = ctx.log.outputStream.println(msg) - - def trace(t: Throwable) = t.printStackTrace(ctx.log.outputStream) - - def info(msg: String) = ctx.log.outputStream.println(msg) - }) - ) - taskQueue.enqueue(next:_*) - } - runner.done() - } - - val results = for(e <- events) yield { - val ex = if (e.throwable().isDefined) Some(e.throwable().get) else None - mill.scalalib.TestRunner.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().toString, - ex.map(_.getClass.getName), - ex.map(_.getMessage), - ex.map(_.getStackTrace) - ) - } - - (doneMessages.mkString("\n"), results) - }) - } - - def frameworks(frameworkNames: Seq[String])(cl: ClassLoader): Seq[sbt.testing.Framework] = { - frameworkNames.map { name => - cl.loadClass(name).newInstance().asInstanceOf[sbt.testing.Framework] - } - } - - case class Result(fullyQualifiedName: String, - selector: String, - duration: Long, - status: String, - exceptionName: Option[String] = None, - exceptionMsg: Option[String] = None, - exceptionTrace: Option[Seq[StackTraceElement]] = None) - - object Result{ - implicit def resultRW: upickle.default.ReadWriter[Result] = upickle.default.macroRW[Result] - } -} diff --git a/scalalib/src/mill/scalalib/Versions.scala b/scalalib/src/mill/scalalib/Versions.scala deleted file mode 100644 index e7eaf847..00000000 --- a/scalalib/src/mill/scalalib/Versions.scala +++ /dev/null @@ -1,8 +0,0 @@ -package mill.scalalib - -object Versions { - // Keep synchronized with ammonite dependency in core in build.sc - val ammonite = "1.5.0" - // Keep synchronized with zinc dependency in scalalib.worker in build.sc - val zinc = "1.2.1" -} diff --git a/scalalib/src/mill/scalalib/ZincWorkerModule.scala b/scalalib/src/mill/scalalib/ZincWorkerModule.scala deleted file mode 100644 index 5ca824ce..00000000 --- a/scalalib/src/mill/scalalib/ZincWorkerModule.scala +++ /dev/null @@ -1,56 +0,0 @@ -package mill.scalalib - -import coursier.Cache -import coursier.maven.MavenRepository -import mill.Agg -import mill.T -import mill.define.{Discover, Worker} -import mill.scalalib.Lib.resolveDependencies -import mill.util.Loose -import mill.util.JsonFormatters._ - -object ZincWorkerModule extends mill.define.ExternalModule with ZincWorkerModule{ - lazy val millDiscover = Discover[this.type] -} -trait ZincWorkerModule extends mill.Module{ - def repositories = Seq( - Cache.ivy2Local, - MavenRepository("https://repo1.maven.org/maven2"), - MavenRepository("https://oss.sonatype.org/content/repositories/releases") - ) - - def classpath = T{ - mill.modules.Util.millProjectModule("MILL_SCALA_WORKER", "mill-scalalib-worker", repositories) - } - - def scalalibClasspath = T{ - mill.modules.Util.millProjectModule("MILL_SCALA_LIB", "mill-scalalib", repositories) - } - - def backgroundWrapperClasspath = T{ - mill.modules.Util.millProjectModule( - "MILL_BACKGROUNDWRAPPER", "mill-scalalib-backgroundwrapper", - repositories, artifactSuffix = "" - ) - } - - def worker: Worker[mill.scalalib.api.ZincWorkerApi] = T.worker{ - val cl = mill.api.ClassLoader.create( - classpath().map(_.path.toNIO.toUri.toURL).toVector, - getClass.getClassLoader - ) - val cls = cl.loadClass("mill.scalalib.worker.ZincWorkerImpl") - val instance = cls.getConstructor(classOf[mill.api.Ctx], classOf[Array[String]]) - .newInstance(T.ctx(), compilerInterfaceClasspath().map(_.path.toString).toArray[String]) - instance.asInstanceOf[mill.scalalib.api.ZincWorkerApi] - } - - def compilerInterfaceClasspath = T{ - resolveDependencies( - repositories, - Lib.depToDependency(_, "2.12.4", ""), - Seq(ivy"org.scala-sbt:compiler-interface:${Versions.zinc}") - ) - } - -} diff --git a/scalalib/src/mill/scalalib/dependency/DependencyUpdatesImpl.scala b/scalalib/src/mill/scalalib/dependency/DependencyUpdatesImpl.scala deleted file mode 100644 index 3bb94202..00000000 --- a/scalalib/src/mill/scalalib/dependency/DependencyUpdatesImpl.scala +++ /dev/null @@ -1,52 +0,0 @@ -package mill.scalalib.dependency - -import mill.define._ -import mill.scalalib.dependency.updates.{ - DependencyUpdates, - ModuleDependenciesUpdates, - UpdatesFinder -} -import mill.scalalib.dependency.versions.VersionsFinder -import mill.api.Ctx.{Home, Log} - -object DependencyUpdatesImpl { - - def apply(ctx: Log with Home, - rootModule: BaseModule, - discover: Discover[_], - allowPreRelease: Boolean): Unit = { - - // 1. Find all available versions for each dependency - val allDependencyVersions = VersionsFinder.findVersions(ctx, rootModule) - - // 2. Extract updated versions from all available versions - val allUpdates = allDependencyVersions.map { dependencyVersions => - UpdatesFinder.findUpdates(dependencyVersions, allowPreRelease) - } - - // 3. Print the results - showAllUpdates(allUpdates) - } - - private def showAllUpdates(updates: Seq[ModuleDependenciesUpdates]): Unit = - updates.foreach { dependencyUpdates => - val module = dependencyUpdates.module.toString - val actualUpdates = - dependencyUpdates.dependencies.filter(_.updates.nonEmpty) - if (actualUpdates.isEmpty) { - println(s"No dependency updates found for $module") - } else { - println(s"Found ${actualUpdates.length} dependency update for $module") - showUpdates(actualUpdates) - } - } - - private def showUpdates(updates: Seq[DependencyUpdates]): Unit = - updates.foreach { dependencyUpdate => - val module = s"${dependencyUpdate.dependency.module}" - val allVersions = - (dependencyUpdate.currentVersion +: dependencyUpdate.updates.toList) - .mkString(" -> ") - println(s" $module : $allVersions") - } -} diff --git a/scalalib/src/mill/scalalib/dependency/metadata/MavenMetadataLoader.scala b/scalalib/src/mill/scalalib/dependency/metadata/MavenMetadataLoader.scala deleted file mode 100644 index 491911bf..00000000 --- a/scalalib/src/mill/scalalib/dependency/metadata/MavenMetadataLoader.scala +++ /dev/null @@ -1,21 +0,0 @@ -package mill.scalalib.dependency.metadata - -import coursier.Cache -import coursier.maven.MavenRepository -import coursier.util.Task -import mill.scalalib.dependency.versions.Version - -private[dependency] final case class MavenMetadataLoader(mavenRepo: MavenRepository) - extends MetadataLoader { - - private val fetch = Cache.fetch[Task]() - - override def getVersions(module: coursier.Module): List[Version] = { - import scala.concurrent.ExecutionContext.Implicits.global - // TODO fallback to 'versionsFromListing' if 'versions' doesn't work? (needs to be made public in coursier first) - val allVersions = mavenRepo.versions(module, fetch).run.unsafeRun - allVersions - .map(_.available.map(Version(_))) - .getOrElse(List.empty) - } -} diff --git a/scalalib/src/mill/scalalib/dependency/metadata/MetadataLoader.scala b/scalalib/src/mill/scalalib/dependency/metadata/MetadataLoader.scala deleted file mode 100644 index 20271f0e..00000000 --- a/scalalib/src/mill/scalalib/dependency/metadata/MetadataLoader.scala +++ /dev/null @@ -1,7 +0,0 @@ -package mill.scalalib.dependency.metadata - -import mill.scalalib.dependency.versions.Version - -private[dependency] trait MetadataLoader { - def getVersions(module: coursier.Module): Seq[Version] -} diff --git a/scalalib/src/mill/scalalib/dependency/metadata/MetadataLoaderFactory.scala b/scalalib/src/mill/scalalib/dependency/metadata/MetadataLoaderFactory.scala deleted file mode 100644 index 4495d6b0..00000000 --- a/scalalib/src/mill/scalalib/dependency/metadata/MetadataLoaderFactory.scala +++ /dev/null @@ -1,11 +0,0 @@ -package mill.scalalib.dependency.metadata - -import coursier.Repository -import coursier.maven.MavenRepository - -private[dependency] object MetadataLoaderFactory { - def apply(repo: Repository): Option[MetadataLoader] = repo match { - case mavenRepo: MavenRepository => Some(MavenMetadataLoader(mavenRepo)) - case _ => None - } -} diff --git a/scalalib/src/mill/scalalib/dependency/updates/ModuleDependenciesUpdates.scala b/scalalib/src/mill/scalalib/dependency/updates/ModuleDependenciesUpdates.scala deleted file mode 100644 index a989cd31..00000000 --- a/scalalib/src/mill/scalalib/dependency/updates/ModuleDependenciesUpdates.scala +++ /dev/null @@ -1,15 +0,0 @@ -package mill.scalalib.dependency.updates - -import mill.scalalib.JavaModule -import mill.scalalib.dependency.versions.Version - -import scala.collection.SortedSet - -private[dependency] final case class ModuleDependenciesUpdates( - module: JavaModule, - dependencies: Seq[DependencyUpdates]) - -private[dependency] final case class DependencyUpdates( - dependency: coursier.Dependency, - currentVersion: Version, - updates: SortedSet[Version]) diff --git a/scalalib/src/mill/scalalib/dependency/updates/UpdatesFinder.scala b/scalalib/src/mill/scalalib/dependency/updates/UpdatesFinder.scala deleted file mode 100644 index 3430592f..00000000 --- a/scalalib/src/mill/scalalib/dependency/updates/UpdatesFinder.scala +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file contains code originally published under the following license: - * - * Copyright (c) 2012, Roman Timushev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package mill.scalalib.dependency.updates - -import mill.scalalib.dependency.versions._ - -import scala.collection.SortedSet - -private[dependency] object UpdatesFinder { - - import scala.Ordered._ - - def findUpdates(dependencyVersions: ModuleDependenciesVersions, - allowPreRelease: Boolean): ModuleDependenciesUpdates = { - val dependencies = - dependencyVersions.dependencies.map { dependencyVersion => - findUpdates(dependencyVersion, allowPreRelease) - } - ModuleDependenciesUpdates(dependencyVersions.module, dependencies) - } - - def findUpdates(dependencyVersion: DependencyVersions, - allowPreRelease: Boolean): DependencyUpdates = { - val current = dependencyVersion.currentVersion - val versions = dependencyVersion.allversions.to[SortedSet] - - val updates = versions - .filter(isUpdate(current)) - .filterNot(lessStable(current, allowPreRelease)) - - DependencyUpdates(dependencyVersion.dependency, - dependencyVersion.currentVersion, - updates) - } - - private def lessStable(current: Version, allowPreRelease: Boolean)( - another: Version): Boolean = (current, another) match { - case (ReleaseVersion(_), ReleaseVersion(_)) => false - case (SnapshotVersion(_, _, _), _) => false - case (_, SnapshotVersion(_, _, _)) => true - case (ReleaseVersion(_), PreReleaseVersion(_, _)) => !allowPreRelease - case (ReleaseVersion(_), PreReleaseBuildVersion(_, _, _)) => - !allowPreRelease - case (ReleaseVersion(_), _) => true - case (_, _) => false - } - - private def isUpdate(current: Version) = current < _ -} diff --git a/scalalib/src/mill/scalalib/dependency/versions/ModuleDependenciesVersions.scala b/scalalib/src/mill/scalalib/dependency/versions/ModuleDependenciesVersions.scala deleted file mode 100644 index 12d57059..00000000 --- a/scalalib/src/mill/scalalib/dependency/versions/ModuleDependenciesVersions.scala +++ /dev/null @@ -1,12 +0,0 @@ -package mill.scalalib.dependency.versions - -import mill.scalalib.JavaModule - -private[dependency] final case class ModuleDependenciesVersions( - module: JavaModule, - dependencies: Seq[DependencyVersions]) - -private[dependency] final case class DependencyVersions( - dependency: coursier.Dependency, - currentVersion: Version, - allversions: Set[Version]) diff --git a/scalalib/src/mill/scalalib/dependency/versions/Version.scala b/scalalib/src/mill/scalalib/dependency/versions/Version.scala deleted file mode 100644 index a2719023..00000000 --- a/scalalib/src/mill/scalalib/dependency/versions/Version.scala +++ /dev/null @@ -1,227 +0,0 @@ -/* - * This file contains code originally published under the following license: - * - * Copyright (c) 2012, Roman Timushev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package mill.scalalib.dependency.versions - -import scala.util.matching.Regex -import scala.util.matching.Regex.Groups - -private[dependency] sealed trait Version { - def major: Long - - def minor: Long - - def patch: Long -} - -private[dependency] case class ValidVersion(text: String, - releasePart: List[Long], - preReleasePart: List[String], - buildPart: List[String]) - extends Version { - def major: Long = releasePart.headOption getOrElse 0 - - def minor: Long = releasePart.drop(1).headOption getOrElse 1 - - def patch: Long = releasePart.drop(2).headOption getOrElse 1 - - override def toString: String = text -} - -private[dependency] case class InvalidVersion(text: String) extends Version { - def major: Long = -1 - - def minor: Long = -1 - - def patch: Long = -1 -} - -private[dependency] object ReleaseVersion { - private val releaseKeyword: Regex = "(?i)final|release".r - - def unapply(v: Version): Option[List[Long]] = v match { - case ValidVersion(_, releasePart, Nil, Nil) => Some(releasePart) - case ValidVersion(_, releasePart, releaseKeyword() :: Nil, Nil) => - Some(releasePart) - case _ => None - } -} - -private[dependency] object PreReleaseVersion { - def unapply(v: Version): Option[(List[Long], List[String])] = v match { - case ValidVersion(_, releasePart, preReleasePart, Nil) - if preReleasePart.nonEmpty => - Some(releasePart, preReleasePart) - case _ => None - } -} - -private[dependency] object PreReleaseBuildVersion { - def unapply(v: Version): Option[(List[Long], List[String], List[String])] = - v match { - case ValidVersion(_, releasePart, preReleasePart, buildPart) - if preReleasePart.nonEmpty && buildPart.nonEmpty => - Some(releasePart, preReleasePart, buildPart) - case _ => None - } -} - -private[dependency] object SnapshotVersion { - def unapply(v: Version): Option[(List[Long], List[String], List[String])] = - v match { - case ValidVersion(_, releasePart, preReleasePart, buildPart) - if preReleasePart.lastOption.contains("SNAPSHOT") => - Some(releasePart, preReleasePart, buildPart) - case _ => None - } -} - -private[dependency] object BuildVersion { - def unapply(v: Version): Option[(List[Long], List[String])] = v match { - case ValidVersion(_, releasePart, Nil, buildPart) if buildPart.nonEmpty => - Some(releasePart, buildPart) - case _ => None - } -} - -private[dependency] object Version { - def apply(text: String): Version = synchronized { - VersionParser - .parse(text) - .fold( - (_, _, _) => InvalidVersion(text), - { case ((a, b, c), _) => ValidVersion(text, a.toList, b.toList, c.toList)} - ) - } - - implicit def versionOrdering: Ordering[Version] = VersionOrdering -} - -private[dependency] object VersionOrdering extends Ordering[Version] { - - private val subParts = "(\\d+)?(\\D+)?".r - - private def parsePart(s: String): Seq[Either[Int, String]] = - try { - subParts - .findAllIn(s) - .matchData - .flatMap { - case Groups(num, str) => - Seq(Option(num).map(_.toInt).map(Left.apply), - Option(str).map(Right.apply)) - } - .flatten - .toList - } catch { - case _: NumberFormatException => List(Right(s)) - } - - private def toOpt(x: Int): Option[Int] = if (x == 0) None else Some(x) - - private def comparePart(a: String, b: String) = { - if (a == b) None - else - (parsePart(a) zip parsePart(b)) map { - case (Left(x), Left(y)) => x compareTo y - case (Left(_), Right(_)) => -1 - case (Right(_), Left(_)) => 1 - case (Right(x), Right(y)) => x compareTo y - } find (0 != _) orElse Some(a compareTo b) - } - - private def compareNumericParts(a: List[Long], b: List[Long]): Option[Int] = - (a, b) match { - case (ah :: at, bh :: bt) => - toOpt(ah compareTo bh) orElse compareNumericParts(at, bt) - case (ah :: at, Nil) => - toOpt(ah compareTo 0L) orElse compareNumericParts(at, Nil) - case (Nil, bh :: bt) => - toOpt(0L compareTo bh) orElse compareNumericParts(Nil, bt) - case (Nil, Nil) => - None - } - - private def compareParts(a: List[String], b: List[String]): Option[Int] = - (a, b) match { - case (ah :: at, bh :: bt) => - comparePart(ah, bh) orElse compareParts(at, bt) - case (_ :: _, Nil) => - Some(1) - case (Nil, _ :: _) => - Some(-1) - case (Nil, Nil) => - None - } - - def compare(x: Version, y: Version): Int = (x, y) match { - case (InvalidVersion(a), InvalidVersion(b)) => - a compareTo b - case (InvalidVersion(_), _) => - -1 - case (_, InvalidVersion(_)) => - 1 - case (ReleaseVersion(r1), ReleaseVersion(r2)) => - compareNumericParts(r1, r2) getOrElse 0 - case (ReleaseVersion(r1), PreReleaseVersion(r2, p2)) => - compareNumericParts(r1, r2) getOrElse 1 - case (ReleaseVersion(r1), PreReleaseBuildVersion(r2, p2, b2)) => - compareNumericParts(r1, r2) getOrElse 1 - case (ReleaseVersion(r1), BuildVersion(r2, b2)) => - compareNumericParts(r1, r2) getOrElse -1 - case (PreReleaseVersion(r1, p1), ReleaseVersion(r2)) => - compareNumericParts(r1, r2) getOrElse -1 - case (PreReleaseVersion(r1, p1), PreReleaseVersion(r2, p2)) => - compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse 0 - case (PreReleaseVersion(r1, p1), PreReleaseBuildVersion(r2, p2, b2)) => - compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse -1 - case (PreReleaseVersion(r1, p1), BuildVersion(r2, b2)) => - compareNumericParts(r1, r2) getOrElse -1 - case (PreReleaseBuildVersion(r1, p1, b1), ReleaseVersion(r2)) => - compareNumericParts(r1, r2) getOrElse -1 - case (PreReleaseBuildVersion(r1, p1, b1), PreReleaseVersion(r2, p2)) => - compareNumericParts(r1, r2) orElse compareParts(p1, p2) getOrElse 1 - case (PreReleaseBuildVersion(r1, p1, b1), - PreReleaseBuildVersion(r2, p2, b2)) => - compareNumericParts(r1, r2) orElse - compareParts(p1, p2) orElse - compareParts(b1, b2) getOrElse - 0 - case (PreReleaseBuildVersion(r1, p1, b1), BuildVersion(r2, b2)) => - compareNumericParts(r1, r2) getOrElse -1 - case (BuildVersion(r1, b1), ReleaseVersion(r2)) => - compareNumericParts(r1, r2) getOrElse 1 - case (BuildVersion(r1, b1), PreReleaseVersion(r2, p2)) => - compareNumericParts(r1, r2) getOrElse 1 - case (BuildVersion(r1, b1), PreReleaseBuildVersion(r2, p2, b2)) => - compareNumericParts(r1, r2) getOrElse 1 - case (BuildVersion(r1, b1), BuildVersion(r2, b2)) => - compareNumericParts(r1, r2) orElse compareParts(b1, b2) getOrElse 0 - } - -} diff --git a/scalalib/src/mill/scalalib/dependency/versions/VersionParser.scala b/scalalib/src/mill/scalalib/dependency/versions/VersionParser.scala deleted file mode 100644 index 10aebd73..00000000 --- a/scalalib/src/mill/scalalib/dependency/versions/VersionParser.scala +++ /dev/null @@ -1,30 +0,0 @@ -package mill.scalalib.dependency.versions - -import fastparse._, NoWhitespace._ - -private[dependency] object VersionParser { - - private def numberParser[_: P] = - P(CharIn("0-9").rep(1).!.map(_.toLong)) - private def numericPartParser[_: P] = - P(numberParser ~ &(CharIn(".\\-+") | End)).rep(min = 1, sep = ".") - - private def tokenParser[_: P] = - CharPred(c => c != '.' && c != '-' && c != '+').rep(1).! - private def tokenPartParser[_: P] = - tokenParser.rep(sep = CharIn(".\\-")) - - private def firstPartParser[_: P] = - P(CharIn(".\\-") ~ tokenPartParser).? - - private def secondPartParser[_: P] = - P("+" ~ tokenPartParser).? - - private def versionParser[_: P] = - P(numericPartParser ~ firstPartParser ~ secondPartParser).map { - case (a, b, c) => (a, b.getOrElse(Seq.empty), c.getOrElse(Seq.empty)) - } - - def parse(text: String): Parsed[(Seq[Long], Seq[String], Seq[String])] = - fastparse.parse(text, versionParser(_)) -} diff --git a/scalalib/src/mill/scalalib/dependency/versions/VersionsFinder.scala b/scalalib/src/mill/scalalib/dependency/versions/VersionsFinder.scala deleted file mode 100644 index a831ffc3..00000000 --- a/scalalib/src/mill/scalalib/dependency/versions/VersionsFinder.scala +++ /dev/null @@ -1,73 +0,0 @@ -package mill.scalalib.dependency.versions - -import mill.define.{BaseModule, Task} -import mill.eval.Evaluator -import mill.scalalib.dependency.metadata.MetadataLoaderFactory -import mill.scalalib.{Dep, JavaModule, Lib} -import mill.api.Ctx.{Home, Log} -import mill.util.{Loose, Strict} - -private[dependency] object VersionsFinder { - - def findVersions(ctx: Log with Home, - rootModule: BaseModule): Seq[ModuleDependenciesVersions] = { - val evaluator = - new Evaluator(ctx.home, os.pwd / 'out, os.pwd / 'out, rootModule, ctx.log) - - val javaModules = rootModule.millInternal.modules.collect { - case javaModule: JavaModule => javaModule - } - - val resolvedDependencies = resolveDependencies(evaluator, javaModules) - resolveVersions(resolvedDependencies) - } - - private def resolveDependencies(evaluator: Evaluator, - javaModules: Seq[JavaModule]) = - javaModules.map { javaModule => - val depToDependency = - eval(evaluator, javaModule.resolveCoursierDependency) - val deps = evalOrElse(evaluator, javaModule.ivyDeps, Loose.Agg.empty[Dep]) - - val (dependencies, _) = - Lib.resolveDependenciesMetadata(javaModule.repositories, - depToDependency, - deps) - - (javaModule, dependencies) - } - - private def resolveVersions(resolvedDependencies: Seq[ResolvedDependencies]) = - resolvedDependencies.map { - case (javaModule, dependencies) => - val metadataLoaders = - javaModule.repositories.flatMap(MetadataLoaderFactory(_)) - - val versions = dependencies.map { dependency => - val currentVersion = Version(dependency.version) - val allVersions = - metadataLoaders - .flatMap(_.getVersions(dependency.module)) - .toSet - DependencyVersions(dependency, currentVersion, allVersions) - } - - ModuleDependenciesVersions(javaModule, versions) - } - - private def eval[T](evaluator: Evaluator, e: Task[T]): T = - evaluator.evaluate(Strict.Agg(e)).values match { - case Seq() => throw new NoSuchElementException - case Seq(e: T) => e - } - - private def evalOrElse[T](evaluator: Evaluator, - e: Task[T], - default: => T): T = - evaluator.evaluate(Strict.Agg(e)).values match { - case Seq() => default - case Seq(e: T) => e - } - - private type ResolvedDependencies = (JavaModule, Seq[coursier.Dependency]) -} diff --git a/scalalib/src/mill/scalalib/package.scala b/scalalib/src/mill/scalalib/package.scala deleted file mode 100644 index 5a282e82..00000000 --- a/scalalib/src/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/mill/scalalib/publish/Ivy.scala b/scalalib/src/mill/scalalib/publish/Ivy.scala deleted file mode 100644 index 22e26ff6..00000000 --- a/scalalib/src/mill/scalalib/publish/Ivy.scala +++ /dev/null @@ -1,59 +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).toSeq} - - - val pp = new PrettyPrinter(120, 4) - head + pp.format(xml).replaceAll(">", ">") - } - - private def renderDependency(dep: Dependency) = { - if (dep.exclusions.isEmpty) - ${dep.configuration.getOrElse("default(compile)")}"} /> - else - ${dep.configuration.getOrElse("default(compile)")}"}> - {dep.exclusions.map(ex => )} - - } - - 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 deleted file mode 100644 index 8fc90632..00000000 --- a/scalalib/src/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 versionControlFormat: RW[VersionControl] = upickle.default.macroRW - implicit lazy val pomSettingsFormat: RW[PomSettings] = upickle.default.macroRW -} diff --git a/scalalib/src/mill/scalalib/publish/Licence.scala b/scalalib/src/mill/scalalib/publish/Licence.scala deleted file mode 100644 index 8838ef69..00000000 --- a/scalalib/src/mill/scalalib/publish/Licence.scala +++ /dev/null @@ -1,479 +0,0 @@ -package mill.scalalib.publish - -case class License( - id: String, - name: String, - url: String, - isOsiApproved: Boolean, - isFsfLibre: Boolean, - distribution: String -) - -object License { - @deprecated("use License.LicenseName (ex: License.`Apache-2.0`)", "0.1.0") - def apply(name: String, url: String): License = - License(name, name, url, false, false, "repo") - - /* - wget https://raw.githubusercontent.com/spdx/license-list-data/master/json/licenses.json - - ``` - val circeVersion = "0.9.1" - libraryDependencies ++= Seq( - "io.circe" %% "circe-core", - "io.circe" %% "circe-generic", - "io.circe" %% "circe-parser" - ).map(_ % circeVersion) - - import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._ - import java.nio.file._ - import System.{lineSeparator => nl} - case class License( - reference: String, - isDeprecatedLicenseId: Boolean, - isFsfLibre: Option[Boolean], - detailsUrl: String, - referenceNumber: String, - name: String, - licenseId: String, - seeAlso: Option[List[String]], - isOsiApproved: Boolean - ) { - def ident: String = { - val startsWithDigit = (0 to 9).map(_.toString).exists(licenseId.startsWith) - if (licenseId.contains("-") || !startsWithDigit) s"`$licenseId`" - else licenseId - } - - def syntax(identPadding: Int, namePadding: Int): String = { - val s1 = " " * (identPadding - ident.size) - val s2 = " " * (namePadding - name.size) - val ticks = if (ident == licenseId) 2 else 0 - val s3 = " " * (identPadding - ticks - ident.size) - val s4 = if(isOsiApproved) " " else "" - s"""val ${ident}${s1} = spdx(\"\"\"$name\"\"\",$s2 "$licenseId", $s3 $isOsiApproved, $s4 ${isFsfLibre.getOrElse(false)})""" - } - } - - - case class Data(licenses: List[License]) - - val json = new String(Files.readAllBytes(Paths.get("data.json"))) - - val licences = decode[Data](json).right.get.licenses - - val identPadding = licences.map(_.licenseId.size + 2).max - val namePadding = licences.map(_.name.size).max - - val output = licences.map(license => license.syntax(identPadding, namePadding)).mkString(nl) - Files.write(Paths.get("out.scala"), output.getBytes("utf-8")) - */ - val `0BSD` = spdx("BSD Zero Clause License", "0BSD", false, false) - val AAL = spdx("Attribution Assurance License", "AAL", true, false) - val Abstyles = spdx("Abstyles License", "Abstyles", false, false) - val `Adobe-2006` = spdx("Adobe Systems Incorporated Source Code License Agreement", "Adobe-2006", false, false) - val `Adobe-Glyph` = spdx("Adobe Glyph List License", "Adobe-Glyph", false, false) - val ADSL = spdx("Amazon Digital Services License", "ADSL", false, false) - val `AFL-1.1` = spdx("Academic Free License v1.1", "AFL-1.1", true, true) - val `AFL-1.2` = spdx("Academic Free License v1.2", "AFL-1.2", true, true) - val `AFL-2.0` = spdx("Academic Free License v2.0", "AFL-2.0", true, true) - val `AFL-2.1` = spdx("Academic Free License v2.1", "AFL-2.1", true, true) - val `AFL-3.0` = spdx("Academic Free License v3.0", "AFL-3.0", true, true) - val Afmparse = spdx("Afmparse License", "Afmparse", false, false) - val `AGPL-1.0` = spdx("Affero General Public License v1.0", "AGPL-1.0", false, true) - val `AGPL-3.0-only` = spdx("GNU Affero General Public License v3.0 only", "AGPL-3.0-only", true, false) - val `AGPL-3.0-or-later` = spdx("GNU Affero General Public License v3.0 or later", "AGPL-3.0-or-later", true, false) - val Aladdin = spdx("Aladdin Free Public License", "Aladdin", false, false) - val AMDPLPA = spdx("AMD's plpa_map.c License", "AMDPLPA", false, false) - val AML = spdx("Apple MIT License", "AML", false, false) - val AMPAS = spdx("Academy of Motion Picture Arts and Sciences BSD", "AMPAS", false, false) - val `ANTLR-PD` = spdx("ANTLR Software Rights Notice", "ANTLR-PD", false, false) - val `Apache-1.0` = spdx("Apache License 1.0", "Apache-1.0", false, true) - val `Apache-1.1` = spdx("Apache License 1.1", "Apache-1.1", true, true) - val `Apache-2.0` = spdx("Apache License 2.0", "Apache-2.0", true, true) - val APAFML = spdx("Adobe Postscript AFM License", "APAFML", false, false) - val `APL-1.0` = spdx("Adaptive Public License 1.0", "APL-1.0", true, false) - val `APSL-1.0` = spdx("Apple Public Source License 1.0", "APSL-1.0", true, false) - val `APSL-1.1` = spdx("Apple Public Source License 1.1", "APSL-1.1", true, false) - val `APSL-1.2` = spdx("Apple Public Source License 1.2", "APSL-1.2", true, false) - val `APSL-2.0` = spdx("Apple Public Source License 2.0", "APSL-2.0", true, true) - val `Artistic-1.0-cl8` = spdx("Artistic License 1.0 w/clause 8", "Artistic-1.0-cl8", true, false) - val `Artistic-1.0-Perl` = spdx("Artistic License 1.0 (Perl)", "Artistic-1.0-Perl", true, false) - val `Artistic-1.0` = spdx("Artistic License 1.0", "Artistic-1.0", true, false) - val `Artistic-2.0` = spdx("Artistic License 2.0", "Artistic-2.0", true, true) - val Bahyph = spdx("Bahyph License", "Bahyph", false, false) - val Barr = spdx("Barr License", "Barr", false, false) - val Beerware = spdx("Beerware License", "Beerware", false, false) - val `BitTorrent-1.0` = spdx("BitTorrent Open Source License v1.0", "BitTorrent-1.0", false, false) - val `BitTorrent-1.1` = spdx("BitTorrent Open Source License v1.1", "BitTorrent-1.1", false, true) - val Borceux = spdx("Borceux license", "Borceux", false, false) - val `BSD-1-Clause` = spdx("BSD 1-Clause License", "BSD-1-Clause", false, false) - val `BSD-2-Clause-FreeBSD` = spdx("BSD 2-Clause FreeBSD License", "BSD-2-Clause-FreeBSD", false, true) - val `BSD-2-Clause-NetBSD` = spdx("BSD 2-Clause NetBSD License", "BSD-2-Clause-NetBSD", false, false) - val `BSD-2-Clause-Patent` = spdx("BSD-2-Clause Plus Patent License", "BSD-2-Clause-Patent", true, false) - val `BSD-2-Clause` = spdx("BSD 2-Clause \"Simplified\" License", "BSD-2-Clause", true, false) - val `BSD-3-Clause-Attribution` = spdx("BSD with attribution", "BSD-3-Clause-Attribution", false, false) - val `BSD-3-Clause-Clear` = spdx("BSD 3-Clause Clear License", "BSD-3-Clause-Clear", false, true) - val `BSD-3-Clause-LBNL` = spdx("Lawrence Berkeley National Labs BSD variant license", "BSD-3-Clause-LBNL", false, false) - val `BSD-3-Clause-No-Nuclear-License-2014` = spdx("BSD 3-Clause No Nuclear License 2014", "BSD-3-Clause-No-Nuclear-License-2014", false, false) - val `BSD-3-Clause-No-Nuclear-License` = spdx("BSD 3-Clause No Nuclear License", "BSD-3-Clause-No-Nuclear-License", false, false) - val `BSD-3-Clause-No-Nuclear-Warranty` = spdx("BSD 3-Clause No Nuclear Warranty", "BSD-3-Clause-No-Nuclear-Warranty", false, false) - val `BSD-3-Clause` = spdx("BSD 3-Clause \"New\" or \"Revised\" License", "BSD-3-Clause", true, true) - val `BSD-4-Clause-UC` = spdx("BSD-4-Clause (University of California-Specific)", "BSD-4-Clause-UC", false, false) - val `BSD-4-Clause` = spdx("BSD 4-Clause \"Original\" or \"Old\" License", "BSD-4-Clause", false, true) - val `BSD-Protection` = spdx("BSD Protection License", "BSD-Protection", false, false) - val `BSD-Source-Code` = spdx("BSD Source Code Attribution", "BSD-Source-Code", false, false) - val `BSL-1.0` = spdx("Boost Software License 1.0", "BSL-1.0", true, true) - val `bzip2-1.0.5` = spdx("bzip2 and libbzip2 License v1.0.5", "bzip2-1.0.5", false, false) - val `bzip2-1.0.6` = spdx("bzip2 and libbzip2 License v1.0.6", "bzip2-1.0.6", false, false) - val Caldera = spdx("Caldera License", "Caldera", false, false) - val `CATOSL-1.1` = spdx("Computer Associates Trusted Open Source License 1.1", "CATOSL-1.1", true, false) - val `CC-BY-1.0` = spdx("Creative Commons Attribution 1.0", "CC-BY-1.0", false, false) - val `CC-BY-2.0` = spdx("Creative Commons Attribution 2.0", "CC-BY-2.0", false, false) - val `CC-BY-2.5` = spdx("Creative Commons Attribution 2.5", "CC-BY-2.5", false, false) - val `CC-BY-3.0` = spdx("Creative Commons Attribution 3.0", "CC-BY-3.0", false, false) - val `CC-BY-4.0` = spdx("Creative Commons Attribution 4.0", "CC-BY-4.0", false, true) - val `CC-BY-NC-1.0` = spdx("Creative Commons Attribution Non Commercial 1.0", "CC-BY-NC-1.0", false, false) - val `CC-BY-NC-2.0` = spdx("Creative Commons Attribution Non Commercial 2.0", "CC-BY-NC-2.0", false, false) - val `CC-BY-NC-2.5` = spdx("Creative Commons Attribution Non Commercial 2.5", "CC-BY-NC-2.5", false, false) - val `CC-BY-NC-3.0` = spdx("Creative Commons Attribution Non Commercial 3.0", "CC-BY-NC-3.0", false, false) - val `CC-BY-NC-4.0` = spdx("Creative Commons Attribution Non Commercial 4.0", "CC-BY-NC-4.0", false, false) - val `CC-BY-NC-ND-1.0` = spdx("Creative Commons Attribution Non Commercial No Derivatives 1.0", "CC-BY-NC-ND-1.0", false, false) - val `CC-BY-NC-ND-2.0` = spdx("Creative Commons Attribution Non Commercial No Derivatives 2.0", "CC-BY-NC-ND-2.0", false, false) - val `CC-BY-NC-ND-2.5` = spdx("Creative Commons Attribution Non Commercial No Derivatives 2.5", "CC-BY-NC-ND-2.5", false, false) - val `CC-BY-NC-ND-3.0` = spdx("Creative Commons Attribution Non Commercial No Derivatives 3.0", "CC-BY-NC-ND-3.0", false, false) - val `CC-BY-NC-ND-4.0` = spdx("Creative Commons Attribution Non Commercial No Derivatives 4.0", "CC-BY-NC-ND-4.0", false, false) - val `CC-BY-NC-SA-1.0` = spdx("Creative Commons Attribution Non Commercial Share Alike 1.0", "CC-BY-NC-SA-1.0", false, false) - val `CC-BY-NC-SA-2.0` = spdx("Creative Commons Attribution Non Commercial Share Alike 2.0", "CC-BY-NC-SA-2.0", false, false) - val `CC-BY-NC-SA-2.5` = spdx("Creative Commons Attribution Non Commercial Share Alike 2.5", "CC-BY-NC-SA-2.5", false, false) - val `CC-BY-NC-SA-3.0` = spdx("Creative Commons Attribution Non Commercial Share Alike 3.0", "CC-BY-NC-SA-3.0", false, false) - val `CC-BY-NC-SA-4.0` = spdx("Creative Commons Attribution Non Commercial Share Alike 4.0", "CC-BY-NC-SA-4.0", false, false) - val `CC-BY-ND-1.0` = spdx("Creative Commons Attribution No Derivatives 1.0", "CC-BY-ND-1.0", false, false) - val `CC-BY-ND-2.0` = spdx("Creative Commons Attribution No Derivatives 2.0", "CC-BY-ND-2.0", false, false) - val `CC-BY-ND-2.5` = spdx("Creative Commons Attribution No Derivatives 2.5", "CC-BY-ND-2.5", false, false) - val `CC-BY-ND-3.0` = spdx("Creative Commons Attribution No Derivatives 3.0", "CC-BY-ND-3.0", false, false) - val `CC-BY-ND-4.0` = spdx("Creative Commons Attribution No Derivatives 4.0", "CC-BY-ND-4.0", false, false) - val `CC-BY-SA-1.0` = spdx("Creative Commons Attribution Share Alike 1.0", "CC-BY-SA-1.0", false, false) - val `CC-BY-SA-2.0` = spdx("Creative Commons Attribution Share Alike 2.0", "CC-BY-SA-2.0", false, false) - val `CC-BY-SA-2.5` = spdx("Creative Commons Attribution Share Alike 2.5", "CC-BY-SA-2.5", false, false) - val `CC-BY-SA-3.0` = spdx("Creative Commons Attribution Share Alike 3.0", "CC-BY-SA-3.0", false, false) - val `CC-BY-SA-4.0` = spdx("Creative Commons Attribution Share Alike 4.0", "CC-BY-SA-4.0", false, true) - val `CC0-1.0` = spdx("Creative Commons Zero v1.0 Universal", "CC0-1.0", false, true) - val `CDDL-1.0` = spdx("Common Development and Distribution License 1.0", "CDDL-1.0", true, true) - val `CDDL-1.1` = spdx("Common Development and Distribution License 1.1", "CDDL-1.1", false, false) - val `CDLA-Permissive-1.0` = spdx("Community Data License Agreement Permissive 1.0", "CDLA-Permissive-1.0", false, false) - val `CDLA-Sharing-1.0` = spdx("Community Data License Agreement Sharing 1.0", "CDLA-Sharing-1.0", false, false) - val `CECILL-1.0` = spdx("CeCILL Free Software License Agreement v1.0", "CECILL-1.0", false, false) - val `CECILL-1.1` = spdx("CeCILL Free Software License Agreement v1.1", "CECILL-1.1", false, false) - val `CECILL-2.0` = spdx("CeCILL Free Software License Agreement v2.0", "CECILL-2.0", false, true) - val `CECILL-2.1` = spdx("CeCILL Free Software License Agreement v2.1", "CECILL-2.1", true, false) - val `CECILL-B` = spdx("CeCILL-B Free Software License Agreement", "CECILL-B", false, true) - val `CECILL-C` = spdx("CeCILL-C Free Software License Agreement", "CECILL-C", false, true) - val ClArtistic = spdx("Clarified Artistic License", "ClArtistic", false, true) - val `CNRI-Jython` = spdx("CNRI Jython License", "CNRI-Jython", false, false) - val `CNRI-Python-GPL-Compatible` = spdx("CNRI Python Open Source GPL Compatible License Agreement", "CNRI-Python-GPL-Compatible", false, false) - val `CNRI-Python` = spdx("CNRI Python License", "CNRI-Python", true, false) - val `Condor-1.1` = spdx("Condor Public License v1.1", "Condor-1.1", false, true) - val `CPAL-1.0` = spdx("Common Public Attribution License 1.0", "CPAL-1.0", true, true) - val `CPL-1.0` = spdx("Common Public License 1.0", "CPL-1.0", true, true) - val `CPOL-1.02` = spdx("Code Project Open License 1.02", "CPOL-1.02", false, false) - val Crossword = spdx("Crossword License", "Crossword", false, false) - val CrystalStacker = spdx("CrystalStacker License", "CrystalStacker", false, false) - val `CUA-OPL-1.0` = spdx("CUA Office Public License v1.0", "CUA-OPL-1.0", true, false) - val Cube = spdx("Cube License", "Cube", false, false) - val curl = spdx("curl License", "curl", false, false) - val `D-FSL-1.0` = spdx("Deutsche Freie Software Lizenz", "D-FSL-1.0", false, false) - val diffmark = spdx("diffmark license", "diffmark", false, false) - val DOC = spdx("DOC License", "DOC", false, false) - val Dotseqn = spdx("Dotseqn License", "Dotseqn", false, false) - val DSDP = spdx("DSDP License", "DSDP", false, false) - val dvipdfm = spdx("dvipdfm License", "dvipdfm", false, false) - val `ECL-1.0` = spdx("Educational Community License v1.0", "ECL-1.0", true, false) - val `ECL-2.0` = spdx("Educational Community License v2.0", "ECL-2.0", true, true) - val `EFL-1.0` = spdx("Eiffel Forum License v1.0", "EFL-1.0", true, false) - val `EFL-2.0` = spdx("Eiffel Forum License v2.0", "EFL-2.0", true, true) - val eGenix = spdx("eGenix.com Public License 1.1.0", "eGenix", false, false) - val Entessa = spdx("Entessa Public License v1.0", "Entessa", true, false) - val `EPL-1.0` = spdx("Eclipse Public License 1.0", "EPL-1.0", true, true) - val `EPL-2.0` = spdx("Eclipse Public License 2.0", "EPL-2.0", true, true) - val `ErlPL-1.1` = spdx("Erlang Public License v1.1", "ErlPL-1.1", false, false) - val EUDatagrid = spdx("EU DataGrid Software License", "EUDatagrid", true, true) - val `EUPL-1.0` = spdx("European Union Public License 1.0", "EUPL-1.0", false, false) - val `EUPL-1.1` = spdx("European Union Public License 1.1", "EUPL-1.1", true, true) - val `EUPL-1.2` = spdx("European Union Public License 1.2", "EUPL-1.2", true, false) - val Eurosym = spdx("Eurosym License", "Eurosym", false, false) - val Fair = spdx("Fair License", "Fair", true, false) - val `Frameworx-1.0` = spdx("Frameworx Open License 1.0", "Frameworx-1.0", true, false) - val FreeImage = spdx("FreeImage Public License v1.0", "FreeImage", false, false) - val FSFAP = spdx("FSF All Permissive License", "FSFAP", false, true) - val FSFUL = spdx("FSF Unlimited License", "FSFUL", false, false) - val FSFULLR = spdx("FSF Unlimited License (with License Retention)", "FSFULLR", false, false) - val FTL = spdx("Freetype Project License", "FTL", false, true) - val `GFDL-1.1-only` = spdx("GNU Free Documentation License v1.1 only", "GFDL-1.1-only", false, false) - val `GFDL-1.1-or-later` = spdx("GNU Free Documentation License v1.1 or later", "GFDL-1.1-or-later", false, false) - val `GFDL-1.2-only` = spdx("GNU Free Documentation License v1.2 only", "GFDL-1.2-only", false, false) - val `GFDL-1.2-or-later` = spdx("GNU Free Documentation License v1.2 or later", "GFDL-1.2-or-later", false, false) - val `GFDL-1.3-only` = spdx("GNU Free Documentation License v1.3 only", "GFDL-1.3-only", false, false) - val `GFDL-1.3-or-later` = spdx("GNU Free Documentation License v1.3 or later", "GFDL-1.3-or-later", false, false) - val Giftware = spdx("Giftware License", "Giftware", false, false) - val GL2PS = spdx("GL2PS License", "GL2PS", false, false) - val Glide = spdx("3dfx Glide License", "Glide", false, false) - val Glulxe = spdx("Glulxe License", "Glulxe", false, false) - val gnuplot = spdx("gnuplot License", "gnuplot", false, true) - val `GPL-1.0-only` = spdx("GNU General Public License v1.0 only", "GPL-1.0-only", false, false) - val `GPL-1.0-or-later` = spdx("GNU General Public License v1.0 or later", "GPL-1.0-or-later", false, false) - val `GPL-2.0-only` = spdx("GNU General Public License v2.0 only", "GPL-2.0-only", true, false) - val `GPL-2.0-or-later` = spdx("GNU General Public License v2.0 or later", "GPL-2.0-or-later", true, false) - val `GPL-3.0-only` = spdx("GNU General Public License v3.0 only", "GPL-3.0-only", true, false) - val `GPL-3.0-or-later` = spdx("GNU General Public License v3.0 or later", "GPL-3.0-or-later", true, false) - val `gSOAP-1.3b` = spdx("gSOAP Public License v1.3b", "gSOAP-1.3b", false, false) - val HaskellReport = spdx("Haskell Language Report License", "HaskellReport", false, false) - val HPND = spdx("Historical Permission Notice and Disclaimer", "HPND", true, true) - val `IBM-pibs` = spdx("IBM PowerPC Initialization and Boot Software", "IBM-pibs", false, false) - val ICU = spdx("ICU License", "ICU", false, false) - val IJG = spdx("Independent JPEG Group License", "IJG", false, true) - val ImageMagick = spdx("ImageMagick License", "ImageMagick", false, false) - val iMatix = spdx("iMatix Standard Function Library Agreement", "iMatix", false, true) - val Imlib2 = spdx("Imlib2 License", "Imlib2", false, true) - val `Info-ZIP` = spdx("Info-ZIP License", "Info-ZIP", false, false) - val `Intel-ACPI` = spdx("Intel ACPI Software License Agreement", "Intel-ACPI", false, false) - val Intel = spdx("Intel Open Source License", "Intel", true, true) - val `Interbase-1.0` = spdx("Interbase Public License v1.0", "Interbase-1.0", false, false) - val IPA = spdx("IPA Font License", "IPA", true, true) - val `IPL-1.0` = spdx("IBM Public License v1.0", "IPL-1.0", true, true) - val ISC = spdx("ISC License", "ISC", true, true) - val `JasPer-2.0` = spdx("JasPer License", "JasPer-2.0", false, false) - val JSON = spdx("JSON License", "JSON", false, false) - val `LAL-1.2` = spdx("Licence Art Libre 1.2", "LAL-1.2", false, false) - val `LAL-1.3` = spdx("Licence Art Libre 1.3", "LAL-1.3", false, false) - val Latex2e = spdx("Latex2e License", "Latex2e", false, false) - val Leptonica = spdx("Leptonica License", "Leptonica", false, false) - val `LGPL-2.0-only` = spdx("GNU Library General Public License v2 only", "LGPL-2.0-only", true, false) - val `LGPL-2.0-or-later` = spdx("GNU Library General Public License v2 or later", "LGPL-2.0-or-later", true, false) - val `LGPL-2.1-only` = spdx("GNU Lesser General Public License v2.1 only", "LGPL-2.1-only", true, false) - val `LGPL-2.1-or-later` = spdx("GNU Lesser General Public License v2.1 or later", "LGPL-2.1-or-later", true, false) - val `LGPL-3.0-only` = spdx("GNU Lesser General Public License v3.0 only", "LGPL-3.0-only", true, false) - val `LGPL-3.0-or-later` = spdx("GNU Lesser General Public License v3.0 or later", "LGPL-3.0-or-later", true, false) - val LGPLLR = spdx("Lesser General Public License For Linguistic Resources", "LGPLLR", false, false) - val Libpng = spdx("libpng License", "Libpng", false, false) - val libtiff = spdx("libtiff License", "libtiff", false, false) - val `LiLiQ-P-1.1` = spdx("Licence Libre du Québec – Permissive version 1.1", "LiLiQ-P-1.1", true, false) - val `LiLiQ-R-1.1` = spdx("Licence Libre du Québec – Réciprocité version 1.1", "LiLiQ-R-1.1", true, false) - val `LiLiQ-Rplus-1.1` = spdx("Licence Libre du Québec – Réciprocité forte version 1.1", "LiLiQ-Rplus-1.1", true, false) - val `LPL-1.0` = spdx("Lucent Public License Version 1.0", "LPL-1.0", true, false) - val `LPL-1.02` = spdx("Lucent Public License v1.02", "LPL-1.02", true, true) - val `LPPL-1.0` = spdx("LaTeX Project Public License v1.0", "LPPL-1.0", false, false) - val `LPPL-1.1` = spdx("LaTeX Project Public License v1.1", "LPPL-1.1", false, false) - val `LPPL-1.2` = spdx("LaTeX Project Public License v1.2", "LPPL-1.2", false, true) - val `LPPL-1.3a` = spdx("LaTeX Project Public License v1.3a", "LPPL-1.3a", false, true) - val `LPPL-1.3c` = spdx("LaTeX Project Public License v1.3c", "LPPL-1.3c", true, false) - val MakeIndex = spdx("MakeIndex License", "MakeIndex", false, false) - val MirOS = spdx("MirOS License", "MirOS", true, false) - val `MIT-advertising` = spdx("Enlightenment License (e16)", "MIT-advertising", false, false) - val `MIT-CMU` = spdx("CMU License", "MIT-CMU", false, false) - val `MIT-enna` = spdx("enna License", "MIT-enna", false, false) - val `MIT-feh` = spdx("feh License", "MIT-feh", false, false) - val MIT = spdx("MIT License", "MIT", true, true) - val MITNFA = spdx("MIT +no-false-attribs license", "MITNFA", false, false) - val Motosoto = spdx("Motosoto License", "Motosoto", true, false) - val mpich2 = spdx("mpich2 License", "mpich2", false, false) - val `MPL-1.0` = spdx("Mozilla Public License 1.0", "MPL-1.0", true, false) - val `MPL-1.1` = spdx("Mozilla Public License 1.1", "MPL-1.1", true, true) - val `MPL-2.0-no-copyleft-exception` = spdx("Mozilla Public License 2.0 (no copyleft exception)", "MPL-2.0-no-copyleft-exception", true, false) - val `MPL-2.0` = spdx("Mozilla Public License 2.0", "MPL-2.0", true, true) - val `MS-PL` = spdx("Microsoft Public License", "MS-PL", true, true) - val `MS-RL` = spdx("Microsoft Reciprocal License", "MS-RL", true, true) - val MTLL = spdx("Matrix Template Library License", "MTLL", false, false) - val Multics = spdx("Multics License", "Multics", true, false) - val Mup = spdx("Mup License", "Mup", false, false) - val `NASA-1.3` = spdx("NASA Open Source Agreement 1.3", "NASA-1.3", true, false) - val Naumen = spdx("Naumen Public License", "Naumen", true, false) - val `NBPL-1.0` = spdx("Net Boolean Public License v1", "NBPL-1.0", false, false) - val NCSA = spdx("University of Illinois/NCSA Open Source License", "NCSA", true, true) - val `Net-SNMP` = spdx("Net-SNMP License", "Net-SNMP", false, false) - val NetCDF = spdx("NetCDF license", "NetCDF", false, false) - val Newsletr = spdx("Newsletr License", "Newsletr", false, false) - val NGPL = spdx("Nethack General Public License", "NGPL", true, false) - val `NLOD-1.0` = spdx("Norwegian Licence for Open Government Data", "NLOD-1.0", false, false) - val NLPL = spdx("No Limit Public License", "NLPL", false, false) - val Nokia = spdx("Nokia Open Source License", "Nokia", true, true) - val NOSL = spdx("Netizen Open Source License", "NOSL", false, true) - val Noweb = spdx("Noweb License", "Noweb", false, false) - val `NPL-1.0` = spdx("Netscape Public License v1.0", "NPL-1.0", false, true) - val `NPL-1.1` = spdx("Netscape Public License v1.1", "NPL-1.1", false, true) - val `NPOSL-3.0` = spdx("Non-Profit Open Software License 3.0", "NPOSL-3.0", true, false) - val NRL = spdx("NRL License", "NRL", false, false) - val NTP = spdx("NTP License", "NTP", true, false) - val `OCCT-PL` = spdx("Open CASCADE Technology Public License", "OCCT-PL", false, false) - val `OCLC-2.0` = spdx("OCLC Research Public License 2.0", "OCLC-2.0", true, false) - val `ODbL-1.0` = spdx("ODC Open Database License v1.0", "ODbL-1.0", false, true) - val `OFL-1.0` = spdx("SIL Open Font License 1.0", "OFL-1.0", false, false) - val `OFL-1.1` = spdx("SIL Open Font License 1.1", "OFL-1.1", true, true) - val OGTSL = spdx("Open Group Test Suite License", "OGTSL", true, false) - val `OLDAP-1.1` = spdx("Open LDAP Public License v1.1", "OLDAP-1.1", false, false) - val `OLDAP-1.2` = spdx("Open LDAP Public License v1.2", "OLDAP-1.2", false, false) - val `OLDAP-1.3` = spdx("Open LDAP Public License v1.3", "OLDAP-1.3", false, false) - val `OLDAP-1.4` = spdx("Open LDAP Public License v1.4", "OLDAP-1.4", false, false) - val `OLDAP-2.0.1` = spdx("Open LDAP Public License v2.0.1", "OLDAP-2.0.1", false, false) - val `OLDAP-2.0` = spdx("Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", "OLDAP-2.0", false, false) - val `OLDAP-2.1` = spdx("Open LDAP Public License v2.1", "OLDAP-2.1", false, false) - val `OLDAP-2.2.1` = spdx("Open LDAP Public License v2.2.1", "OLDAP-2.2.1", false, false) - val `OLDAP-2.2.2` = spdx("Open LDAP Public License 2.2.2", "OLDAP-2.2.2", false, false) - val `OLDAP-2.2` = spdx("Open LDAP Public License v2.2", "OLDAP-2.2", false, false) - val `OLDAP-2.3` = spdx("Open LDAP Public License v2.3", "OLDAP-2.3", false, true) - val `OLDAP-2.4` = spdx("Open LDAP Public License v2.4", "OLDAP-2.4", false, false) - val `OLDAP-2.5` = spdx("Open LDAP Public License v2.5", "OLDAP-2.5", false, false) - val `OLDAP-2.6` = spdx("Open LDAP Public License v2.6", "OLDAP-2.6", false, false) - val `OLDAP-2.7` = spdx("Open LDAP Public License v2.7", "OLDAP-2.7", false, true) - val `OLDAP-2.8` = spdx("Open LDAP Public License v2.8", "OLDAP-2.8", false, false) - val OML = spdx("Open Market License", "OML", false, false) - val OpenSSL = spdx("OpenSSL License", "OpenSSL", false, true) - val `OPL-1.0` = spdx("Open Public License v1.0", "OPL-1.0", false, false) - val `OSET-PL-2.1` = spdx("OSET Public License version 2.1", "OSET-PL-2.1", true, false) - val `OSL-1.0` = spdx("Open Software License 1.0", "OSL-1.0", true, true) - val `OSL-1.1` = spdx("Open Software License 1.1", "OSL-1.1", false, true) - val `OSL-2.0` = spdx("Open Software License 2.0", "OSL-2.0", true, true) - val `OSL-2.1` = spdx("Open Software License 2.1", "OSL-2.1", true, true) - val `OSL-3.0` = spdx("Open Software License 3.0", "OSL-3.0", true, true) - val `PDDL-1.0` = spdx("ODC Public Domain Dedication & License 1.0", "PDDL-1.0", false, false) - val `PHP-3.0` = spdx("PHP License v3.0", "PHP-3.0", true, false) - val `PHP-3.01` = spdx("PHP License v3.01", "PHP-3.01", false, true) - val Plexus = spdx("Plexus Classworlds License", "Plexus", false, false) - val PostgreSQL = spdx("PostgreSQL License", "PostgreSQL", true, false) - val psfrag = spdx("psfrag License", "psfrag", false, false) - val psutils = spdx("psutils License", "psutils", false, false) - val `Python-2.0` = spdx("Python License 2.0", "Python-2.0", true, true) - val Qhull = spdx("Qhull License", "Qhull", false, false) - val `QPL-1.0` = spdx("Q Public License 1.0", "QPL-1.0", true, true) - val Rdisc = spdx("Rdisc License", "Rdisc", false, false) - val `RHeCos-1.1` = spdx("Red Hat eCos Public License v1.1", "RHeCos-1.1", false, false) - val `RPL-1.1` = spdx("Reciprocal Public License 1.1", "RPL-1.1", true, false) - val `RPL-1.5` = spdx("Reciprocal Public License 1.5", "RPL-1.5", true, false) - val `RPSL-1.0` = spdx("RealNetworks Public Source License v1.0", "RPSL-1.0", true, true) - val `RSA-MD` = spdx("RSA Message-Digest License ", "RSA-MD", false, false) - val RSCPL = spdx("Ricoh Source Code Public License", "RSCPL", true, false) - val Ruby = spdx("Ruby License", "Ruby", false, true) - val `SAX-PD` = spdx("Sax Public Domain Notice", "SAX-PD", false, false) - val Saxpath = spdx("Saxpath License", "Saxpath", false, false) - val SCEA = spdx("SCEA Shared Source License", "SCEA", false, false) - val Sendmail = spdx("Sendmail License", "Sendmail", false, false) - val `SGI-B-1.0` = spdx("SGI Free Software License B v1.0", "SGI-B-1.0", false, false) - val `SGI-B-1.1` = spdx("SGI Free Software License B v1.1", "SGI-B-1.1", false, false) - val `SGI-B-2.0` = spdx("SGI Free Software License B v2.0", "SGI-B-2.0", false, true) - val `SimPL-2.0` = spdx("Simple Public License 2.0", "SimPL-2.0", true, false) - val `SISSL-1.2` = spdx("Sun Industry Standards Source License v1.2", "SISSL-1.2", false, false) - val SISSL = spdx("Sun Industry Standards Source License v1.1", "SISSL", true, false) - val Sleepycat = spdx("Sleepycat License", "Sleepycat", true, true) - val SMLNJ = spdx("Standard ML of New Jersey License", "SMLNJ", false, true) - val SMPPL = spdx("Secure Messaging Protocol Public License", "SMPPL", false, false) - val SNIA = spdx("SNIA Public License 1.1", "SNIA", false, false) - val `Spencer-86` = spdx("Spencer License 86", "Spencer-86", false, false) - val `Spencer-94` = spdx("Spencer License 94", "Spencer-94", false, false) - val `Spencer-99` = spdx("Spencer License 99", "Spencer-99", false, false) - val `SPL-1.0` = spdx("Sun Public License v1.0", "SPL-1.0", true, true) - val `SugarCRM-1.1.3` = spdx("SugarCRM Public License v1.1.3", "SugarCRM-1.1.3", false, false) - val SWL = spdx("Scheme Widget Library (SWL) Software License Agreement", "SWL", false, false) - val TCL = spdx("TCL/TK License", "TCL", false, false) - val `TCP-wrappers` = spdx("TCP Wrappers License", "TCP-wrappers", false, false) - val TMate = spdx("TMate Open Source License", "TMate", false, false) - val `TORQUE-1.1` = spdx("TORQUE v2.5+ Software License v1.1", "TORQUE-1.1", false, false) - val TOSL = spdx("Trusster Open Source License", "TOSL", false, false) - val `Unicode-DFS-2015` = spdx("Unicode License Agreement - Data Files and Software (2015)", "Unicode-DFS-2015", false, false) - val `Unicode-DFS-2016` = spdx("Unicode License Agreement - Data Files and Software (2016)", "Unicode-DFS-2016", false, false) - val `Unicode-TOU` = spdx("Unicode Terms of Use", "Unicode-TOU", false, false) - val Unlicense = spdx("The Unlicense", "Unlicense", false, true) - val `UPL-1.0` = spdx("Universal Permissive License v1.0", "UPL-1.0", true, true) - val Vim = spdx("Vim License", "Vim", false, true) - val VOSTROM = spdx("VOSTROM Public License for Open Source", "VOSTROM", false, false) - val `VSL-1.0` = spdx("Vovida Software License v1.0", "VSL-1.0", true, false) - val `W3C-19980720` = spdx("W3C Software Notice and License (1998-07-20)", "W3C-19980720", false, false) - val `W3C-20150513` = spdx("W3C Software Notice and Document License (2015-05-13)", "W3C-20150513", false, false) - val W3C = spdx("W3C Software Notice and License (2002-12-31)", "W3C", true, true) - val `Watcom-1.0` = spdx("Sybase Open Watcom Public License 1.0", "Watcom-1.0", true, false) - val Wsuipa = spdx("Wsuipa License", "Wsuipa", false, false) - val WTFPL = spdx("Do What The F*ck You Want To Public License", "WTFPL", false, true) - val X11 = spdx("X11 License", "X11", false, true) - val Xerox = spdx("Xerox License", "Xerox", false, false) - val `XFree86-1.1` = spdx("XFree86 License 1.1", "XFree86-1.1", false, true) - val xinetd = spdx("xinetd License", "xinetd", false, true) - val Xnet = spdx("X.Net License", "Xnet", true, false) - val xpp = spdx("XPP License", "xpp", false, false) - val XSkat = spdx("XSkat License", "XSkat", false, false) - val `YPL-1.0` = spdx("Yahoo! Public License v1.0", "YPL-1.0", false, false) - val `YPL-1.1` = spdx("Yahoo! Public License v1.1", "YPL-1.1", false, true) - val Zed = spdx("Zed License", "Zed", false, false) - val `Zend-2.0` = spdx("Zend License v2.0", "Zend-2.0", false, true) - val `Zimbra-1.3` = spdx("Zimbra Public License v1.3", "Zimbra-1.3", false, true) - val `Zimbra-1.4` = spdx("Zimbra Public License v1.4", "Zimbra-1.4", false, false) - val `zlib-acknowledgement` = spdx("zlib/libpng License with Acknowledgement", "zlib-acknowledgement", false, false) - val Zlib = spdx("zlib License", "Zlib", true, true) - val `ZPL-1.1` = spdx("Zope Public License 1.1", "ZPL-1.1", false, false) - val `ZPL-2.0` = spdx("Zope Public License 2.0", "ZPL-2.0", true, true) - val `ZPL-2.1` = spdx("Zope Public License 2.1", "ZPL-2.1", false, true) - val `AGPL-3.0` = spdx("GNU Affero General Public License v3.0", "AGPL-3.0", true, false) - val `eCos-2.0` = spdx("eCos license version 2.0", "eCos-2.0", false, false) - val `GFDL-1.1` = spdx("GNU Free Documentation License v1.1", "GFDL-1.1", false, false) - val `GFDL-1.2` = spdx("GNU Free Documentation License v1.2", "GFDL-1.2", false, false) - val `GFDL-1.3` = spdx("GNU Free Documentation License v1.3", "GFDL-1.3", false, false) - val `GPL-1.0+` = spdx("GNU General Public License v1.0 or later", "GPL-1.0+", false, false) - val `GPL-1.0` = spdx("GNU General Public License v1.0 only", "GPL-1.0", false, false) - val `GPL-2.0+` = spdx("GNU General Public License v2.0 or later", "GPL-2.0+", true, false) - val `GPL-2.0-with-autoconf-exception` = spdx("GNU General Public License v2.0 w/Autoconf exception", "GPL-2.0-with-autoconf-exception", false, false) - val `GPL-2.0-with-bison-exception` = spdx("GNU General Public License v2.0 w/Bison exception", "GPL-2.0-with-bison-exception", false, false) - val `GPL-2.0-with-classpath-exception` = spdx("GNU General Public License v2.0 w/Classpath exception", "GPL-2.0-with-classpath-exception", false, false) - val `GPL-2.0-with-font-exception` = spdx("GNU General Public License v2.0 w/Font exception", "GPL-2.0-with-font-exception", false, false) - val `GPL-2.0-with-GCC-exception` = spdx("GNU General Public License v2.0 w/GCC Runtime Library exception", "GPL-2.0-with-GCC-exception", false, false) - val `GPL-2.0` = spdx("GNU General Public License v2.0 only", "GPL-2.0", true, false) - val `GPL-3.0+` = spdx("GNU General Public License v3.0 or later", "GPL-3.0+", true, false) - val `GPL-3.0-with-autoconf-exception` = spdx("GNU General Public License v3.0 w/Autoconf exception", "GPL-3.0-with-autoconf-exception", false, false) - val `GPL-3.0-with-GCC-exception` = spdx("GNU General Public License v3.0 w/GCC Runtime Library exception", "GPL-3.0-with-GCC-exception", true, false) - val `GPL-3.0` = spdx("GNU General Public License v3.0 only", "GPL-3.0", true, false) - val `LGPL-2.0+` = spdx("GNU Library General Public License v2 or later", "LGPL-2.0+", true, false) - val `LGPL-2.0` = spdx("GNU Library General Public License v2 only", "LGPL-2.0", true, false) - val `LGPL-2.1+` = spdx("GNU Library General Public License v2 or later", "LGPL-2.1+", true, false) - val `LGPL-2.1` = spdx("GNU Lesser General Public License v2.1 only", "LGPL-2.1", true, false) - val `LGPL-3.0+` = spdx("GNU Lesser General Public License v3.0 or later", "LGPL-3.0+", true, false) - val `LGPL-3.0` = spdx("GNU Lesser General Public License v3.0 only", "LGPL-3.0", true, false) - val Nunit = spdx("Nunit License", "Nunit", false, false) - val `StandardML-NJ` = spdx("Standard ML of New Jersey License", "StandardML-NJ", false, false) - val wxWindows = spdx("wxWindows Library License", "wxWindows", false, false) - - private def spdx(fullName: String, id: String, isOsiApproved: Boolean, isFsfLibre: Boolean): License = - License(fullName, id, s"https://spdx.org/licenses/$id.html", isOsiApproved, isFsfLibre, "repo") - - val PublicDomain = License( - id = "Public Domain", - name = "Public Domain", - url = "https://creativecommons.org/publicdomain/zero/1.0/", - isOsiApproved = true, // sort of: https://opensource.org/faq#public-domain - isFsfLibre = true, // I'm not sure about this - distribution = "repo" - ) - - val Scala = License( - id = "Scala License", - name = "Scala License", - url = "http://www.scala-lang.org/license.html", - isOsiApproved = false, - isFsfLibre = false, - distribution = "repo" - ) - - val TypesafeSubscriptionAgreement = License( - id = "Typesafe Subscription Agreement", - name = "Typesafe Subscription Agreement", - url = "http://downloads.typesafe.com/website/legal/TypesafeSubscriptionAgreement.pdf", - isOsiApproved = false, - isFsfLibre = false, - distribution = "repo" - ) - - // https://github.com/sbt/sbt/issues/1937#issuecomment-214963983 - object Common { - val Apache2 = License.`Apache-2.0` - val MIT = License.MIT - val BSD4 = License.`BSD-4-Clause` - val Typesafe = License.TypesafeSubscriptionAgreement - val BSD3 = License.`BSD-3-Clause` - } -} \ No newline at end of file diff --git a/scalalib/src/mill/scalalib/publish/LocalPublisher.scala b/scalalib/src/mill/scalalib/publish/LocalPublisher.scala deleted file mode 100644 index d9839831..00000000 --- a/scalalib/src/mill/scalalib/publish/LocalPublisher.scala +++ /dev/null @@ -1,32 +0,0 @@ -package mill.scalalib.publish - - -object LocalPublisher { - - private val root: os.Path = os.home / ".ivy2" / "local" - - def publish(jar: os.Path, - sourcesJar: os.Path, - docJar: os.Path, - pom: os.Path, - ivy: os.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", - docJar -> releaseDir / "docs" / s"${artifact.id}-javadoc.jar", - pom -> releaseDir / "poms" / s"${artifact.id}.pom", - ivy -> releaseDir / "ivys" / "ivy.xml" - ) - } - - private def writeFiles(fromTo: (os.Path, os.Path)*): Unit = { - fromTo.foreach { - case (from, to) => - os.makeDir.all(to / os.up) - os.copy.over(from, to) - } - } - -} diff --git a/scalalib/src/mill/scalalib/publish/Pom.scala b/scalalib/src/mill/scalalib/publish/Pom.scala deleted file mode 100644 index 57a0e196..00000000 --- a/scalalib/src/mill/scalalib/publish/Pom.scala +++ /dev/null @@ -1,117 +0,0 @@ -package mill.scalalib.publish - -import mill.util.Loose.Agg - -import scala.xml.{Atom, Elem, NodeSeq, PrettyPrinter} - -object Pom { - - val head = "\n" - - implicit class XmlOps(val e: Elem) extends AnyVal { - // source: https://stackoverflow.com/a/5254068/449071 - def optional : NodeSeq = { - require(e.child.length == 1) - e.child.head match { - case atom: Atom[Option[_]] => atom.data match { - case None => NodeSeq.Empty - case Some(x) => e.copy(child = x match { - case n: NodeSeq => n - case x => new Atom(x) - }) - } - case _ => e - } - } - } - - //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.versionControl.connection}.optional } - { {pomSettings.versionControl.developerConnection}.optional } - { {pomSettings.versionControl.tag}.optional } - { {pomSettings.versionControl.browsableRepository}.optional } - - - {pomSettings.developers.map(renderDeveloper)} - - - {dependencies.map(renderDependency).toSeq} - - - - 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}.optional } - { {d.organizationUrl}.optional } - - } - - 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 - } - if (d.exclusions.isEmpty) - - {d.artifact.group} - {d.artifact.id} - {d.artifact.version} - {scope} - - else - - {d.artifact.group} - {d.artifact.id} - {d.artifact.version} - - {d.exclusions.map(ex => - - {ex._1} - {ex._2} - - )} - - {scope} - - } - -} diff --git a/scalalib/src/mill/scalalib/publish/SonatypeHttpApi.scala b/scalalib/src/mill/scalalib/publish/SonatypeHttpApi.scala deleted file mode 100644 index 12defa93..00000000 --- a/scalalib/src/mill/scalalib/publish/SonatypeHttpApi.scala +++ /dev/null @@ -1,134 +0,0 @@ -package mill.scalalib.publish - -import java.util.Base64 - - - -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)) - .throwError - - val resourceUri = - ujson - .read(response.body)("data") - .arr - .find(profile => - groupId.split('.').startsWith(profile("name").str.split('.'))) - .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 - .throwError - - ujson.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}"}}""")) - .throwError - - ujson.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 deleted file mode 100644 index 1843943b..00000000 --- a/scalalib/src/mill/scalalib/publish/SonatypePublisher.scala +++ /dev/null @@ -1,164 +0,0 @@ -package mill.scalalib.publish - -import java.math.BigInteger -import java.security.MessageDigest - -import mill.api.Logger - -import scalaj.http.HttpResponse - -class SonatypePublisher(uri: String, - snapshotUri: String, - credentials: String, - gpgPassphrase: Option[String], - signed: Boolean, - log: Logger) { - - private val api = new SonatypeHttpApi(uri, credentials) - - def publish(fileMapping: Seq[(os.Path, String)], artifact: Artifact, release: Boolean): Unit = { - publishAll(release, fileMapping -> artifact) - } - def publishAll(release: Boolean, artifacts: (Seq[(os.Path, String)], Artifact)*): Unit = { - - val mappings = for ((fileMapping0, artifact) <- artifacts) yield { - val publishPath = Seq( - artifact.group.replace(".", "/"), - artifact.id, - artifact.version - ).mkString("/") - val fileMapping = fileMapping0.map{ case (file, name) => (file, publishPath+"/"+name) } - - val signedArtifacts = if (signed) fileMapping.map { - case (file, name) => poorMansSign(file, gpgPassphrase) -> s"$name.asc" - } else Seq() - - artifact -> (fileMapping ++ signedArtifacts).flatMap { - case (file, name) => - val content = os.read.bytes(file) - - Seq( - name -> content, - (name + ".md5") -> md5hex(content), - (name + ".sha1") -> sha1hex(content) - ) - } - } - - val (snapshots, releases) = mappings.partition(_._1.isSnapshot) - if(snapshots.nonEmpty) { - publishSnapshot(snapshots.flatMap(_._2), snapshots.map(_._1)) - } - val releaseGroups = releases.groupBy(_._1.group) - for((group, groupReleases) <- releaseGroups){ - publishRelease(release, groupReleases.flatMap(_._2), group, releases.map(_._1)) - } - } - - private def publishSnapshot(payloads: Seq[(String, Array[Byte])], - artifacts: Seq[Artifact]): Unit = { - - val publishResults = payloads.map { - case (fileName, data) => - log.info(s"Uploading $fileName") - val resp = api.upload(s"$snapshotUri/$fileName", data) - resp - } - reportPublishResults(publishResults, artifacts) - } - - private def publishRelease(release: Boolean, - payloads: Seq[(String, Array[Byte])], - stagingProfile: String, - artifacts: Seq[Artifact]): Unit = { - val profileUri = api.getStagingProfileUri(stagingProfile) - val stagingRepoId = - api.createStagingRepo(profileUri, stagingProfile) - val baseUri = s"$uri/staging/deployByRepositoryId/$stagingRepoId/" - - val publishResults = payloads.map { - case (fileName, data) => - log.info(s"Uploading ${fileName}") - api.upload(s"$baseUri/$fileName", data) - } - reportPublishResults(publishResults, artifacts) - - if (release) { - 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 ${artifacts.map(_.id).mkString(", ")} successfully") - } - } - - private def reportPublishResults(publishResults: Seq[HttpResponse[String]], - artifacts: Seq[Artifact]) = { - if (publishResults.forall(_.is2xx)) { - log.info(s"Published ${artifacts.map(_.id).mkString(", ")} to Sonatype") - } else { - val errors = publishResults.filterNot(_.is2xx).map { response => - s"Code: ${response.code}, message: ${response.body}" - } - throw new RuntimeException( - s"Failed to publish ${artifacts.map(_.id).mkString(", ")} 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: os.Path, maybePassphrase: Option[String]): os.Path = { - val fileName = file.toString - maybePassphrase match { - case Some(passphrase) => - os.proc("gpg", "--passphrase", passphrase, "--batch", "--yes", "-a", "-b", fileName) - .call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit) - case None => - os.proc("gpg", "--batch", "--yes", "-a", "-b", fileName) - .call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit) - } - os.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/VersionControl.scala b/scalalib/src/mill/scalalib/publish/VersionControl.scala deleted file mode 100644 index aad38ac3..00000000 --- a/scalalib/src/mill/scalalib/publish/VersionControl.scala +++ /dev/null @@ -1,131 +0,0 @@ -package mill.scalalib.publish - -// https://maven.apache.org/pom.html#SCM -/* - * @param browsableRepository: a publicly browsable repository - * (example: https://github.com/lihaoyi/mill) - * @param connection: read-only connection to repository - * (example: scm:git:git://github.com/lihaoyi/mill.git) - * @param developerConnection: read-write connection to repository - * (example: scm:git:git@github.com:lihaoyi/mill.git) - * @param tag: tag that was created for this release. This is useful for - * git and mercurial since it's not possible to include the tag in - * the connection url. - * (example: v2.12.4, HEAD, my-branch, fd8a2567ad32c11bcf8adbaca85bdba72bb4f935, ...) - */ -case class VersionControl( - browsableRepository: Option[String] = None, - connection: Option[String] = None, - developerConnection: Option[String] = None, - tag: Option[String] = None -) - -@deprecated("use VersionControl", "0.1.3") -case class SCM( - url: String, - connection: String -) - -object VersionControl { - def github(owner: String, repo: String, tag: Option[String] = None): VersionControl = - VersionControl( - browsableRepository = Some(s"https://github.com/$owner/$repo"), - connection = Some(VersionControlConnection.gitGit("github.com", s"$owner/$repo.git")), - developerConnection = Some(VersionControlConnection.gitSsh("github.com", s":$owner/$repo.git", username = Some("git"))), - tag = tag - ) - def gitlab(owner: String, repo: String, tag: Option[String] = None): VersionControl = - VersionControl( - browsableRepository = Some(s"https://gitlab.com/$owner/$repo"), - connection = Some(VersionControlConnection.gitGit("gitlab.com", s"$owner/$repo.git")), - developerConnection = Some(VersionControlConnection.gitSsh("gitlab.com", s":$owner/$repo.git", username = Some("git"))), - tag = tag - ) -} - -object VersionControlConnection { - def network(scm: String, - protocol: String, - hostname: String, - path: String, - username: Option[String] = None, - password: Option[String] = None, - port: Option[Int] = None): String = { - val portPart = port.map(":" + _).getOrElse("") - val credentials = - username match { - case Some(user) => - val pass = password.map(":" + _).getOrElse("") - s"${user}${pass}@" - case None => - password match { - case Some(p) => sys.error(s"no username set for password: $p") - case _ => "" - } - } - - val path0 = - if(path.startsWith(":") || path.startsWith("/")) path - else "/" + path - - s"scm:${scm}:${protocol}://${credentials}${hostname}${portPart}${path0}" - } - - def file(scm: String, path: String): String = { - s"scm:$scm:file://$path" - } - - def gitGit(hostname: String, - path: String = "", - port: Option[Int] = None): String = - network("git", "git", hostname, path, port = port) - - def gitHttp(hostname: String, - path: String = "", - port: Option[Int] = None): String = - network("git", "http", hostname, path, port = port) - - def gitHttps(hostname: String, - path: String = "", - port: Option[Int] = None): String = - network("git", "https", hostname, path, port = port) - - def gitSsh(hostname: String, - path: String = "", - username: Option[String] = None, - port: Option[Int] = None): String = - network("git", "ssh", hostname, path, username = username, port = port) - - def gitFile(path: String): String = - file("git", path) - - def svnSsh(hostname: String, - path: String = "", - username: Option[String] = None, - port: Option[Int] = None): String = - network("svn", "svn+ssh", hostname, path, username, None, port) - - def svnHttp(hostname: String, - path: String = "", - username: Option[String] = None, - password: Option[String] = None, - port: Option[Int] = None): String = - network("svn", "http", hostname, path, username, password, port) - - def svnHttps(hostname: String, - path: String = "", - username: Option[String] = None, - password: Option[String] = None, - port: Option[Int] = None): String = - network("svn", "https", hostname, path, username, password, port) - - def svnSvn(hostname: String, - path: String = "", - username: Option[String] = None, - password: Option[String] = None, - port: Option[Int] = None): String = - network("svn", "svn", hostname, path, username, password, port) - - def svnFile(path: String): String = - file("svn", path) -} \ No newline at end of file diff --git a/scalalib/src/mill/scalalib/publish/package.scala b/scalalib/src/mill/scalalib/publish/package.scala deleted file mode 100644 index 99eeec14..00000000 --- a/scalalib/src/mill/scalalib/publish/package.scala +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index bca81cf0..00000000 --- a/scalalib/src/mill/scalalib/publish/settings.scala +++ /dev/null @@ -1,91 +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 fromDepJava(dep: Dep) = { - assert(dep.cross.isConstant, s"Not a Java dependency: $dep") - fromDep(dep, "", "", "") - } - - def fromDep(dep: Dep, - scalaFull: String, - scalaBin: String, - platformSuffix: String): Dependency = { - val name = dep.artifactName( - binaryVersion = scalaBin, - fullVersion = scalaFull, - platformSuffix = platformSuffix - ) - Dependency( - Artifact( - dep.dep.module.organization, - name, - dep.dep.version - ), - Scope.Compile, - if (dep.dep.configuration == "") None else Some(dep.dep.configuration), - dep.dep.exclusions.toList - ) - } -} - -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, - configuration: Option[String] = None, - exclusions: Seq[(String, String)] = Nil -) - -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], - versionControl: VersionControl, - developers: Seq[Developer] -) - -object PomSettings { - @deprecated("use VersionControl instead of SCM", "0.1.3") - def apply(description: String, - organization: String, - url: String, - licenses: Seq[License], - scm: SCM, - developers: Seq[Developer]): PomSettings = { - PomSettings( - description = description, - organization = organization, - url = url, - licenses = licenses, - versionControl = VersionControl( - browsableRepository = Some(scm.url), - connection = Some(scm.connection), - developerConnection = None, - tag = None - ), - developers = developers - ) - } -} diff --git a/scalalib/src/mill/scalalib/scalafmt/ScalafmtModule.scala b/scalalib/src/mill/scalalib/scalafmt/ScalafmtModule.scala deleted file mode 100644 index 6a81d975..00000000 --- a/scalalib/src/mill/scalalib/scalafmt/ScalafmtModule.scala +++ /dev/null @@ -1,57 +0,0 @@ -package mill.scalalib.scalafmt - -import mill._ -import mill.define._ -import mill.scalalib._ - -trait ScalafmtModule extends JavaModule { - - def reformat(): Command[Unit] = T.command { - ScalafmtWorkerModule - .worker() - .reformat( - filesToFormat(sources()), - scalafmtConfig().head, - scalafmtDeps().map(_.path) - ) - } - - def scalafmtVersion: T[String] = "1.5.1" - - def scalafmtConfig: Sources = T.sources(os.pwd / ".scalafmt.conf") - - def scalafmtDeps: T[Agg[PathRef]] = T { - Lib.resolveDependencies( - zincWorker.repositories, - Lib.depToDependency(_, "2.12.4"), - Seq(ivy"com.geirsson::scalafmt-cli:${scalafmtVersion()}") - ) - } - - protected def filesToFormat(sources: Seq[PathRef]) = { - for { - pathRef <- sources if os.exists(pathRef.path) - file <- os.walk(pathRef.path) if os.isFile(file) && file.ext == "scala" - } yield PathRef(file) - } - -} - -object ScalafmtModule extends ExternalModule with ScalafmtModule { - - def reformatAll(sources: mill.main.Tasks[Seq[PathRef]]): Command[Unit] = - T.command { - val files = Task.sequence(sources.value)().flatMap(filesToFormat) - ScalafmtWorkerModule - .worker() - .reformat( - files, - scalafmtConfig().head, - scalafmtDeps().map(_.path) - ) - } - - implicit def millScoptTargetReads[T] = new mill.main.Tasks.Scopt[T]() - - lazy val millDiscover = Discover[this.type] -} diff --git a/scalalib/src/mill/scalalib/scalafmt/ScalafmtWorker.scala b/scalalib/src/mill/scalalib/scalafmt/ScalafmtWorker.scala deleted file mode 100644 index 47d8375f..00000000 --- a/scalalib/src/mill/scalalib/scalafmt/ScalafmtWorker.scala +++ /dev/null @@ -1,57 +0,0 @@ -package mill.scalalib.scalafmt - -import mill._ -import mill.define.{Discover, ExternalModule, Worker} -import mill.modules.Jvm -import mill.api.Ctx - -import scala.collection.mutable - -object ScalafmtWorkerModule extends ExternalModule { - def worker: Worker[ScalafmtWorker] = T.worker { new ScalafmtWorker() } - - lazy val millDiscover = Discover[this.type] -} - -private[scalafmt] class ScalafmtWorker { - private val reformatted: mutable.Map[os.Path, Int] = mutable.Map.empty - private var configSig: Int = 0 - - def reformat(input: Seq[PathRef], - scalafmtConfig: PathRef, - scalafmtClasspath: Agg[os.Path])(implicit ctx: Ctx): Unit = { - val toFormat = - if (scalafmtConfig.sig != configSig) input - else - input.filterNot(ref => reformatted.get(ref.path).contains(ref.sig)) - - if (toFormat.nonEmpty) { - ctx.log.info(s"Formatting ${toFormat.size} Scala sources") - reformatAction(toFormat.map(_.path), - scalafmtConfig.path, - scalafmtClasspath) - reformatted ++= toFormat.map { ref => - val updRef = PathRef(ref.path) - updRef.path -> updRef.sig - } - configSig = scalafmtConfig.sig - } else { - ctx.log.info(s"Everything is formatted already") - } - } - - private val cliFlags = Seq("--non-interactive", "--quiet") - - private def reformatAction(toFormat: Seq[os.Path], - config: os.Path, - classpath: Agg[os.Path])(implicit ctx: Ctx) = { - val configFlags = - if (os.exists(config)) Seq("--config", config.toString) else Seq.empty - Jvm.runSubprocess( - "org.scalafmt.cli.Cli", - classpath, - mainArgs = toFormat.map(_.toString) ++ configFlags ++ cliFlags - ) - } - -} diff --git a/scalalib/src/package.scala b/scalalib/src/package.scala new file mode 100644 index 00000000..5a282e82 --- /dev/null +++ b/scalalib/src/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/publish/Ivy.scala b/scalalib/src/publish/Ivy.scala new file mode 100644 index 00000000..22e26ff6 --- /dev/null +++ b/scalalib/src/publish/Ivy.scala @@ -0,0 +1,59 @@ +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).toSeq} + + + val pp = new PrettyPrinter(120, 4) + head + pp.format(xml).replaceAll(">", ">") + } + + private def renderDependency(dep: Dependency) = { + if (dep.exclusions.isEmpty) + ${dep.configuration.getOrElse("default(compile)")}"} /> + else + ${dep.configuration.getOrElse("default(compile)")}"}> + {dep.exclusions.map(ex => )} + + } + + 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/publish/JsonFormatters.scala b/scalalib/src/publish/JsonFormatters.scala new file mode 100644 index 00000000..8fc90632 --- /dev/null +++ b/scalalib/src/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 versionControlFormat: RW[VersionControl] = upickle.default.macroRW + implicit lazy val pomSettingsFormat: RW[PomSettings] = upickle.default.macroRW +} diff --git a/scalalib/src/publish/Licence.scala b/scalalib/src/publish/Licence.scala new file mode 100644 index 00000000..8838ef69 --- /dev/null +++ b/scalalib/src/publish/Licence.scala @@ -0,0 +1,479 @@ +package mill.scalalib.publish + +case class License( + id: String, + name: String, + url: String, + isOsiApproved: Boolean, + isFsfLibre: Boolean, + distribution: String +) + +object License { + @deprecated("use License.LicenseName (ex: License.`Apache-2.0`)", "0.1.0") + def apply(name: String, url: String): License = + License(name, name, url, false, false, "repo") + + /* + wget https://raw.githubusercontent.com/spdx/license-list-data/master/json/licenses.json + + ``` + val circeVersion = "0.9.1" + libraryDependencies ++= Seq( + "io.circe" %% "circe-core", + "io.circe" %% "circe-generic", + "io.circe" %% "circe-parser" + ).map(_ % circeVersion) + + import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._ + import java.nio.file._ + import System.{lineSeparator => nl} + case class License( + reference: String, + isDeprecatedLicenseId: Boolean, + isFsfLibre: Option[Boolean], + detailsUrl: String, + referenceNumber: String, + name: String, + licenseId: String, + seeAlso: Option[List[String]], + isOsiApproved: Boolean + ) { + def ident: String = { + val startsWithDigit = (0 to 9).map(_.toString).exists(licenseId.startsWith) + if (licenseId.contains("-") || !startsWithDigit) s"`$licenseId`" + else licenseId + } + + def syntax(identPadding: Int, namePadding: Int): String = { + val s1 = " " * (identPadding - ident.size) + val s2 = " " * (namePadding - name.size) + val ticks = if (ident == licenseId) 2 else 0 + val s3 = " " * (identPadding - ticks - ident.size) + val s4 = if(isOsiApproved) " " else "" + s"""val ${ident}${s1} = spdx(\"\"\"$name\"\"\",$s2 "$licenseId", $s3 $isOsiApproved, $s4 ${isFsfLibre.getOrElse(false)})""" + } + } + + + case class Data(licenses: List[License]) + + val json = new String(Files.readAllBytes(Paths.get("data.json"))) + + val licences = decode[Data](json).right.get.licenses + + val identPadding = licences.map(_.licenseId.size + 2).max + val namePadding = licences.map(_.name.size).max + + val output = licences.map(license => license.syntax(identPadding, namePadding)).mkString(nl) + Files.write(Paths.get("out.scala"), output.getBytes("utf-8")) + */ + val `0BSD` = spdx("BSD Zero Clause License", "0BSD", false, false) + val AAL = spdx("Attribution Assurance License", "AAL", true, false) + val Abstyles = spdx("Abstyles License", "Abstyles", false, false) + val `Adobe-2006` = spdx("Adobe Systems Incorporated Source Code License Agreement", "Adobe-2006", false, false) + val `Adobe-Glyph` = spdx("Adobe Glyph List License", "Adobe-Glyph", false, false) + val ADSL = spdx("Amazon Digital Services License", "ADSL", false, false) + val `AFL-1.1` = spdx("Academic Free License v1.1", "AFL-1.1", true, true) + val `AFL-1.2` = spdx("Academic Free License v1.2", "AFL-1.2", true, true) + val `AFL-2.0` = spdx("Academic Free License v2.0", "AFL-2.0", true, true) + val `AFL-2.1` = spdx("Academic Free License v2.1", "AFL-2.1", true, true) + val `AFL-3.0` = spdx("Academic Free License v3.0", "AFL-3.0", true, true) + val Afmparse = spdx("Afmparse License", "Afmparse", false, false) + val `AGPL-1.0` = spdx("Affero General Public License v1.0", "AGPL-1.0", false, true) + val `AGPL-3.0-only` = spdx("GNU Affero General Public License v3.0 only", "AGPL-3.0-only", true, false) + val `AGPL-3.0-or-later` = spdx("GNU Affero General Public License v3.0 or later", "AGPL-3.0-or-later", true, false) + val Aladdin = spdx("Aladdin Free Public License", "Aladdin", false, false) + val AMDPLPA = spdx("AMD's plpa_map.c License", "AMDPLPA", false, false) + val AML = spdx("Apple MIT License", "AML", false, false) + val AMPAS = spdx("Academy of Motion Picture Arts and Sciences BSD", "AMPAS", false, false) + val `ANTLR-PD` = spdx("ANTLR Software Rights Notice", "ANTLR-PD", false, false) + val `Apache-1.0` = spdx("Apache License 1.0", "Apache-1.0", false, true) + val `Apache-1.1` = spdx("Apache License 1.1", "Apache-1.1", true, true) + val `Apache-2.0` = spdx("Apache License 2.0", "Apache-2.0", true, true) + val APAFML = spdx("Adobe Postscript AFM License", "APAFML", false, false) + val `APL-1.0` = spdx("Adaptive Public License 1.0", "APL-1.0", true, false) + val `APSL-1.0` = spdx("Apple Public Source License 1.0", "APSL-1.0", true, false) + val `APSL-1.1` = spdx("Apple Public Source License 1.1", "APSL-1.1", true, false) + val `APSL-1.2` = spdx("Apple Public Source License 1.2", "APSL-1.2", true, false) + val `APSL-2.0` = spdx("Apple Public Source License 2.0", "APSL-2.0", true, true) + val `Artistic-1.0-cl8` = spdx("Artistic License 1.0 w/clause 8", "Artistic-1.0-cl8", true, false) + val `Artistic-1.0-Perl` = spdx("Artistic License 1.0 (Perl)", "Artistic-1.0-Perl", true, false) + val `Artistic-1.0` = spdx("Artistic License 1.0", "Artistic-1.0", true, false) + val `Artistic-2.0` = spdx("Artistic License 2.0", "Artistic-2.0", true, true) + val Bahyph = spdx("Bahyph License", "Bahyph", false, false) + val Barr = spdx("Barr License", "Barr", false, false) + val Beerware = spdx("Beerware License", "Beerware", false, false) + val `BitTorrent-1.0` = spdx("BitTorrent Open Source License v1.0", "BitTorrent-1.0", false, false) + val `BitTorrent-1.1` = spdx("BitTorrent Open Source License v1.1", "BitTorrent-1.1", false, true) + val Borceux = spdx("Borceux license", "Borceux", false, false) + val `BSD-1-Clause` = spdx("BSD 1-Clause License", "BSD-1-Clause", false, false) + val `BSD-2-Clause-FreeBSD` = spdx("BSD 2-Clause FreeBSD License", "BSD-2-Clause-FreeBSD", false, true) + val `BSD-2-Clause-NetBSD` = spdx("BSD 2-Clause NetBSD License", "BSD-2-Clause-NetBSD", false, false) + val `BSD-2-Clause-Patent` = spdx("BSD-2-Clause Plus Patent License", "BSD-2-Clause-Patent", true, false) + val `BSD-2-Clause` = spdx("BSD 2-Clause \"Simplified\" License", "BSD-2-Clause", true, false) + val `BSD-3-Clause-Attribution` = spdx("BSD with attribution", "BSD-3-Clause-Attribution", false, false) + val `BSD-3-Clause-Clear` = spdx("BSD 3-Clause Clear License", "BSD-3-Clause-Clear", false, true) + val `BSD-3-Clause-LBNL` = spdx("Lawrence Berkeley National Labs BSD variant license", "BSD-3-Clause-LBNL", false, false) + val `BSD-3-Clause-No-Nuclear-License-2014` = spdx("BSD 3-Clause No Nuclear License 2014", "BSD-3-Clause-No-Nuclear-License-2014", false, false) + val `BSD-3-Clause-No-Nuclear-License` = spdx("BSD 3-Clause No Nuclear License", "BSD-3-Clause-No-Nuclear-License", false, false) + val `BSD-3-Clause-No-Nuclear-Warranty` = spdx("BSD 3-Clause No Nuclear Warranty", "BSD-3-Clause-No-Nuclear-Warranty", false, false) + val `BSD-3-Clause` = spdx("BSD 3-Clause \"New\" or \"Revised\" License", "BSD-3-Clause", true, true) + val `BSD-4-Clause-UC` = spdx("BSD-4-Clause (University of California-Specific)", "BSD-4-Clause-UC", false, false) + val `BSD-4-Clause` = spdx("BSD 4-Clause \"Original\" or \"Old\" License", "BSD-4-Clause", false, true) + val `BSD-Protection` = spdx("BSD Protection License", "BSD-Protection", false, false) + val `BSD-Source-Code` = spdx("BSD Source Code Attribution", "BSD-Source-Code", false, false) + val `BSL-1.0` = spdx("Boost Software License 1.0", "BSL-1.0", true, true) + val `bzip2-1.0.5` = spdx("bzip2 and libbzip2 License v1.0.5", "bzip2-1.0.5", false, false) + val `bzip2-1.0.6` = spdx("bzip2 and libbzip2 License v1.0.6", "bzip2-1.0.6", false, false) + val Caldera = spdx("Caldera License", "Caldera", false, false) + val `CATOSL-1.1` = spdx("Computer Associates Trusted Open Source License 1.1", "CATOSL-1.1", true, false) + val `CC-BY-1.0` = spdx("Creative Commons Attribution 1.0", "CC-BY-1.0", false, false) + val `CC-BY-2.0` = spdx("Creative Commons Attribution 2.0", "CC-BY-2.0", false, false) + val `CC-BY-2.5` = spdx("Creative Commons Attribution 2.5", "CC-BY-2.5", false, false) + val `CC-BY-3.0` = spdx("Creative Commons Attribution 3.0", "CC-BY-3.0", false, false) + val `CC-BY-4.0` = spdx("Creative Commons Attribution 4.0", "CC-BY-4.0", false, true) + val `CC-BY-NC-1.0` = spdx("Creative Commons Attribution Non Commercial 1.0", "CC-BY-NC-1.0", false, false) + val `CC-BY-NC-2.0` = spdx("Creative Commons Attribution Non Commercial 2.0", "CC-BY-NC-2.0", false, false) + val `CC-BY-NC-2.5` = spdx("Creative Commons Attribution Non Commercial 2.5", "CC-BY-NC-2.5", false, false) + val `CC-BY-NC-3.0` = spdx("Creative Commons Attribution Non Commercial 3.0", "CC-BY-NC-3.0", false, false) + val `CC-BY-NC-4.0` = spdx("Creative Commons Attribution Non Commercial 4.0", "CC-BY-NC-4.0", false, false) + val `CC-BY-NC-ND-1.0` = spdx("Creative Commons Attribution Non Commercial No Derivatives 1.0", "CC-BY-NC-ND-1.0", false, false) + val `CC-BY-NC-ND-2.0` = spdx("Creative Commons Attribution Non Commercial No Derivatives 2.0", "CC-BY-NC-ND-2.0", false, false) + val `CC-BY-NC-ND-2.5` = spdx("Creative Commons Attribution Non Commercial No Derivatives 2.5", "CC-BY-NC-ND-2.5", false, false) + val `CC-BY-NC-ND-3.0` = spdx("Creative Commons Attribution Non Commercial No Derivatives 3.0", "CC-BY-NC-ND-3.0", false, false) + val `CC-BY-NC-ND-4.0` = spdx("Creative Commons Attribution Non Commercial No Derivatives 4.0", "CC-BY-NC-ND-4.0", false, false) + val `CC-BY-NC-SA-1.0` = spdx("Creative Commons Attribution Non Commercial Share Alike 1.0", "CC-BY-NC-SA-1.0", false, false) + val `CC-BY-NC-SA-2.0` = spdx("Creative Commons Attribution Non Commercial Share Alike 2.0", "CC-BY-NC-SA-2.0", false, false) + val `CC-BY-NC-SA-2.5` = spdx("Creative Commons Attribution Non Commercial Share Alike 2.5", "CC-BY-NC-SA-2.5", false, false) + val `CC-BY-NC-SA-3.0` = spdx("Creative Commons Attribution Non Commercial Share Alike 3.0", "CC-BY-NC-SA-3.0", false, false) + val `CC-BY-NC-SA-4.0` = spdx("Creative Commons Attribution Non Commercial Share Alike 4.0", "CC-BY-NC-SA-4.0", false, false) + val `CC-BY-ND-1.0` = spdx("Creative Commons Attribution No Derivatives 1.0", "CC-BY-ND-1.0", false, false) + val `CC-BY-ND-2.0` = spdx("Creative Commons Attribution No Derivatives 2.0", "CC-BY-ND-2.0", false, false) + val `CC-BY-ND-2.5` = spdx("Creative Commons Attribution No Derivatives 2.5", "CC-BY-ND-2.5", false, false) + val `CC-BY-ND-3.0` = spdx("Creative Commons Attribution No Derivatives 3.0", "CC-BY-ND-3.0", false, false) + val `CC-BY-ND-4.0` = spdx("Creative Commons Attribution No Derivatives 4.0", "CC-BY-ND-4.0", false, false) + val `CC-BY-SA-1.0` = spdx("Creative Commons Attribution Share Alike 1.0", "CC-BY-SA-1.0", false, false) + val `CC-BY-SA-2.0` = spdx("Creative Commons Attribution Share Alike 2.0", "CC-BY-SA-2.0", false, false) + val `CC-BY-SA-2.5` = spdx("Creative Commons Attribution Share Alike 2.5", "CC-BY-SA-2.5", false, false) + val `CC-BY-SA-3.0` = spdx("Creative Commons Attribution Share Alike 3.0", "CC-BY-SA-3.0", false, false) + val `CC-BY-SA-4.0` = spdx("Creative Commons Attribution Share Alike 4.0", "CC-BY-SA-4.0", false, true) + val `CC0-1.0` = spdx("Creative Commons Zero v1.0 Universal", "CC0-1.0", false, true) + val `CDDL-1.0` = spdx("Common Development and Distribution License 1.0", "CDDL-1.0", true, true) + val `CDDL-1.1` = spdx("Common Development and Distribution License 1.1", "CDDL-1.1", false, false) + val `CDLA-Permissive-1.0` = spdx("Community Data License Agreement Permissive 1.0", "CDLA-Permissive-1.0", false, false) + val `CDLA-Sharing-1.0` = spdx("Community Data License Agreement Sharing 1.0", "CDLA-Sharing-1.0", false, false) + val `CECILL-1.0` = spdx("CeCILL Free Software License Agreement v1.0", "CECILL-1.0", false, false) + val `CECILL-1.1` = spdx("CeCILL Free Software License Agreement v1.1", "CECILL-1.1", false, false) + val `CECILL-2.0` = spdx("CeCILL Free Software License Agreement v2.0", "CECILL-2.0", false, true) + val `CECILL-2.1` = spdx("CeCILL Free Software License Agreement v2.1", "CECILL-2.1", true, false) + val `CECILL-B` = spdx("CeCILL-B Free Software License Agreement", "CECILL-B", false, true) + val `CECILL-C` = spdx("CeCILL-C Free Software License Agreement", "CECILL-C", false, true) + val ClArtistic = spdx("Clarified Artistic License", "ClArtistic", false, true) + val `CNRI-Jython` = spdx("CNRI Jython License", "CNRI-Jython", false, false) + val `CNRI-Python-GPL-Compatible` = spdx("CNRI Python Open Source GPL Compatible License Agreement", "CNRI-Python-GPL-Compatible", false, false) + val `CNRI-Python` = spdx("CNRI Python License", "CNRI-Python", true, false) + val `Condor-1.1` = spdx("Condor Public License v1.1", "Condor-1.1", false, true) + val `CPAL-1.0` = spdx("Common Public Attribution License 1.0", "CPAL-1.0", true, true) + val `CPL-1.0` = spdx("Common Public License 1.0", "CPL-1.0", true, true) + val `CPOL-1.02` = spdx("Code Project Open License 1.02", "CPOL-1.02", false, false) + val Crossword = spdx("Crossword License", "Crossword", false, false) + val CrystalStacker = spdx("CrystalStacker License", "CrystalStacker", false, false) + val `CUA-OPL-1.0` = spdx("CUA Office Public License v1.0", "CUA-OPL-1.0", true, false) + val Cube = spdx("Cube License", "Cube", false, false) + val curl = spdx("curl License", "curl", false, false) + val `D-FSL-1.0` = spdx("Deutsche Freie Software Lizenz", "D-FSL-1.0", false, false) + val diffmark = spdx("diffmark license", "diffmark", false, false) + val DOC = spdx("DOC License", "DOC", false, false) + val Dotseqn = spdx("Dotseqn License", "Dotseqn", false, false) + val DSDP = spdx("DSDP License", "DSDP", false, false) + val dvipdfm = spdx("dvipdfm License", "dvipdfm", false, false) + val `ECL-1.0` = spdx("Educational Community License v1.0", "ECL-1.0", true, false) + val `ECL-2.0` = spdx("Educational Community License v2.0", "ECL-2.0", true, true) + val `EFL-1.0` = spdx("Eiffel Forum License v1.0", "EFL-1.0", true, false) + val `EFL-2.0` = spdx("Eiffel Forum License v2.0", "EFL-2.0", true, true) + val eGenix = spdx("eGenix.com Public License 1.1.0", "eGenix", false, false) + val Entessa = spdx("Entessa Public License v1.0", "Entessa", true, false) + val `EPL-1.0` = spdx("Eclipse Public License 1.0", "EPL-1.0", true, true) + val `EPL-2.0` = spdx("Eclipse Public License 2.0", "EPL-2.0", true, true) + val `ErlPL-1.1` = spdx("Erlang Public License v1.1", "ErlPL-1.1", false, false) + val EUDatagrid = spdx("EU DataGrid Software License", "EUDatagrid", true, true) + val `EUPL-1.0` = spdx("European Union Public License 1.0", "EUPL-1.0", false, false) + val `EUPL-1.1` = spdx("European Union Public License 1.1", "EUPL-1.1", true, true) + val `EUPL-1.2` = spdx("European Union Public License 1.2", "EUPL-1.2", true, false) + val Eurosym = spdx("Eurosym License", "Eurosym", false, false) + val Fair = spdx("Fair License", "Fair", true, false) + val `Frameworx-1.0` = spdx("Frameworx Open License 1.0", "Frameworx-1.0", true, false) + val FreeImage = spdx("FreeImage Public License v1.0", "FreeImage", false, false) + val FSFAP = spdx("FSF All Permissive License", "FSFAP", false, true) + val FSFUL = spdx("FSF Unlimited License", "FSFUL", false, false) + val FSFULLR = spdx("FSF Unlimited License (with License Retention)", "FSFULLR", false, false) + val FTL = spdx("Freetype Project License", "FTL", false, true) + val `GFDL-1.1-only` = spdx("GNU Free Documentation License v1.1 only", "GFDL-1.1-only", false, false) + val `GFDL-1.1-or-later` = spdx("GNU Free Documentation License v1.1 or later", "GFDL-1.1-or-later", false, false) + val `GFDL-1.2-only` = spdx("GNU Free Documentation License v1.2 only", "GFDL-1.2-only", false, false) + val `GFDL-1.2-or-later` = spdx("GNU Free Documentation License v1.2 or later", "GFDL-1.2-or-later", false, false) + val `GFDL-1.3-only` = spdx("GNU Free Documentation License v1.3 only", "GFDL-1.3-only", false, false) + val `GFDL-1.3-or-later` = spdx("GNU Free Documentation License v1.3 or later", "GFDL-1.3-or-later", false, false) + val Giftware = spdx("Giftware License", "Giftware", false, false) + val GL2PS = spdx("GL2PS License", "GL2PS", false, false) + val Glide = spdx("3dfx Glide License", "Glide", false, false) + val Glulxe = spdx("Glulxe License", "Glulxe", false, false) + val gnuplot = spdx("gnuplot License", "gnuplot", false, true) + val `GPL-1.0-only` = spdx("GNU General Public License v1.0 only", "GPL-1.0-only", false, false) + val `GPL-1.0-or-later` = spdx("GNU General Public License v1.0 or later", "GPL-1.0-or-later", false, false) + val `GPL-2.0-only` = spdx("GNU General Public License v2.0 only", "GPL-2.0-only", true, false) + val `GPL-2.0-or-later` = spdx("GNU General Public License v2.0 or later", "GPL-2.0-or-later", true, false) + val `GPL-3.0-only` = spdx("GNU General Public License v3.0 only", "GPL-3.0-only", true, false) + val `GPL-3.0-or-later` = spdx("GNU General Public License v3.0 or later", "GPL-3.0-or-later", true, false) + val `gSOAP-1.3b` = spdx("gSOAP Public License v1.3b", "gSOAP-1.3b", false, false) + val HaskellReport = spdx("Haskell Language Report License", "HaskellReport", false, false) + val HPND = spdx("Historical Permission Notice and Disclaimer", "HPND", true, true) + val `IBM-pibs` = spdx("IBM PowerPC Initialization and Boot Software", "IBM-pibs", false, false) + val ICU = spdx("ICU License", "ICU", false, false) + val IJG = spdx("Independent JPEG Group License", "IJG", false, true) + val ImageMagick = spdx("ImageMagick License", "ImageMagick", false, false) + val iMatix = spdx("iMatix Standard Function Library Agreement", "iMatix", false, true) + val Imlib2 = spdx("Imlib2 License", "Imlib2", false, true) + val `Info-ZIP` = spdx("Info-ZIP License", "Info-ZIP", false, false) + val `Intel-ACPI` = spdx("Intel ACPI Software License Agreement", "Intel-ACPI", false, false) + val Intel = spdx("Intel Open Source License", "Intel", true, true) + val `Interbase-1.0` = spdx("Interbase Public License v1.0", "Interbase-1.0", false, false) + val IPA = spdx("IPA Font License", "IPA", true, true) + val `IPL-1.0` = spdx("IBM Public License v1.0", "IPL-1.0", true, true) + val ISC = spdx("ISC License", "ISC", true, true) + val `JasPer-2.0` = spdx("JasPer License", "JasPer-2.0", false, false) + val JSON = spdx("JSON License", "JSON", false, false) + val `LAL-1.2` = spdx("Licence Art Libre 1.2", "LAL-1.2", false, false) + val `LAL-1.3` = spdx("Licence Art Libre 1.3", "LAL-1.3", false, false) + val Latex2e = spdx("Latex2e License", "Latex2e", false, false) + val Leptonica = spdx("Leptonica License", "Leptonica", false, false) + val `LGPL-2.0-only` = spdx("GNU Library General Public License v2 only", "LGPL-2.0-only", true, false) + val `LGPL-2.0-or-later` = spdx("GNU Library General Public License v2 or later", "LGPL-2.0-or-later", true, false) + val `LGPL-2.1-only` = spdx("GNU Lesser General Public License v2.1 only", "LGPL-2.1-only", true, false) + val `LGPL-2.1-or-later` = spdx("GNU Lesser General Public License v2.1 or later", "LGPL-2.1-or-later", true, false) + val `LGPL-3.0-only` = spdx("GNU Lesser General Public License v3.0 only", "LGPL-3.0-only", true, false) + val `LGPL-3.0-or-later` = spdx("GNU Lesser General Public License v3.0 or later", "LGPL-3.0-or-later", true, false) + val LGPLLR = spdx("Lesser General Public License For Linguistic Resources", "LGPLLR", false, false) + val Libpng = spdx("libpng License", "Libpng", false, false) + val libtiff = spdx("libtiff License", "libtiff", false, false) + val `LiLiQ-P-1.1` = spdx("Licence Libre du Québec – Permissive version 1.1", "LiLiQ-P-1.1", true, false) + val `LiLiQ-R-1.1` = spdx("Licence Libre du Québec – Réciprocité version 1.1", "LiLiQ-R-1.1", true, false) + val `LiLiQ-Rplus-1.1` = spdx("Licence Libre du Québec – Réciprocité forte version 1.1", "LiLiQ-Rplus-1.1", true, false) + val `LPL-1.0` = spdx("Lucent Public License Version 1.0", "LPL-1.0", true, false) + val `LPL-1.02` = spdx("Lucent Public License v1.02", "LPL-1.02", true, true) + val `LPPL-1.0` = spdx("LaTeX Project Public License v1.0", "LPPL-1.0", false, false) + val `LPPL-1.1` = spdx("LaTeX Project Public License v1.1", "LPPL-1.1", false, false) + val `LPPL-1.2` = spdx("LaTeX Project Public License v1.2", "LPPL-1.2", false, true) + val `LPPL-1.3a` = spdx("LaTeX Project Public License v1.3a", "LPPL-1.3a", false, true) + val `LPPL-1.3c` = spdx("LaTeX Project Public License v1.3c", "LPPL-1.3c", true, false) + val MakeIndex = spdx("MakeIndex License", "MakeIndex", false, false) + val MirOS = spdx("MirOS License", "MirOS", true, false) + val `MIT-advertising` = spdx("Enlightenment License (e16)", "MIT-advertising", false, false) + val `MIT-CMU` = spdx("CMU License", "MIT-CMU", false, false) + val `MIT-enna` = spdx("enna License", "MIT-enna", false, false) + val `MIT-feh` = spdx("feh License", "MIT-feh", false, false) + val MIT = spdx("MIT License", "MIT", true, true) + val MITNFA = spdx("MIT +no-false-attribs license", "MITNFA", false, false) + val Motosoto = spdx("Motosoto License", "Motosoto", true, false) + val mpich2 = spdx("mpich2 License", "mpich2", false, false) + val `MPL-1.0` = spdx("Mozilla Public License 1.0", "MPL-1.0", true, false) + val `MPL-1.1` = spdx("Mozilla Public License 1.1", "MPL-1.1", true, true) + val `MPL-2.0-no-copyleft-exception` = spdx("Mozilla Public License 2.0 (no copyleft exception)", "MPL-2.0-no-copyleft-exception", true, false) + val `MPL-2.0` = spdx("Mozilla Public License 2.0", "MPL-2.0", true, true) + val `MS-PL` = spdx("Microsoft Public License", "MS-PL", true, true) + val `MS-RL` = spdx("Microsoft Reciprocal License", "MS-RL", true, true) + val MTLL = spdx("Matrix Template Library License", "MTLL", false, false) + val Multics = spdx("Multics License", "Multics", true, false) + val Mup = spdx("Mup License", "Mup", false, false) + val `NASA-1.3` = spdx("NASA Open Source Agreement 1.3", "NASA-1.3", true, false) + val Naumen = spdx("Naumen Public License", "Naumen", true, false) + val `NBPL-1.0` = spdx("Net Boolean Public License v1", "NBPL-1.0", false, false) + val NCSA = spdx("University of Illinois/NCSA Open Source License", "NCSA", true, true) + val `Net-SNMP` = spdx("Net-SNMP License", "Net-SNMP", false, false) + val NetCDF = spdx("NetCDF license", "NetCDF", false, false) + val Newsletr = spdx("Newsletr License", "Newsletr", false, false) + val NGPL = spdx("Nethack General Public License", "NGPL", true, false) + val `NLOD-1.0` = spdx("Norwegian Licence for Open Government Data", "NLOD-1.0", false, false) + val NLPL = spdx("No Limit Public License", "NLPL", false, false) + val Nokia = spdx("Nokia Open Source License", "Nokia", true, true) + val NOSL = spdx("Netizen Open Source License", "NOSL", false, true) + val Noweb = spdx("Noweb License", "Noweb", false, false) + val `NPL-1.0` = spdx("Netscape Public License v1.0", "NPL-1.0", false, true) + val `NPL-1.1` = spdx("Netscape Public License v1.1", "NPL-1.1", false, true) + val `NPOSL-3.0` = spdx("Non-Profit Open Software License 3.0", "NPOSL-3.0", true, false) + val NRL = spdx("NRL License", "NRL", false, false) + val NTP = spdx("NTP License", "NTP", true, false) + val `OCCT-PL` = spdx("Open CASCADE Technology Public License", "OCCT-PL", false, false) + val `OCLC-2.0` = spdx("OCLC Research Public License 2.0", "OCLC-2.0", true, false) + val `ODbL-1.0` = spdx("ODC Open Database License v1.0", "ODbL-1.0", false, true) + val `OFL-1.0` = spdx("SIL Open Font License 1.0", "OFL-1.0", false, false) + val `OFL-1.1` = spdx("SIL Open Font License 1.1", "OFL-1.1", true, true) + val OGTSL = spdx("Open Group Test Suite License", "OGTSL", true, false) + val `OLDAP-1.1` = spdx("Open LDAP Public License v1.1", "OLDAP-1.1", false, false) + val `OLDAP-1.2` = spdx("Open LDAP Public License v1.2", "OLDAP-1.2", false, false) + val `OLDAP-1.3` = spdx("Open LDAP Public License v1.3", "OLDAP-1.3", false, false) + val `OLDAP-1.4` = spdx("Open LDAP Public License v1.4", "OLDAP-1.4", false, false) + val `OLDAP-2.0.1` = spdx("Open LDAP Public License v2.0.1", "OLDAP-2.0.1", false, false) + val `OLDAP-2.0` = spdx("Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)", "OLDAP-2.0", false, false) + val `OLDAP-2.1` = spdx("Open LDAP Public License v2.1", "OLDAP-2.1", false, false) + val `OLDAP-2.2.1` = spdx("Open LDAP Public License v2.2.1", "OLDAP-2.2.1", false, false) + val `OLDAP-2.2.2` = spdx("Open LDAP Public License 2.2.2", "OLDAP-2.2.2", false, false) + val `OLDAP-2.2` = spdx("Open LDAP Public License v2.2", "OLDAP-2.2", false, false) + val `OLDAP-2.3` = spdx("Open LDAP Public License v2.3", "OLDAP-2.3", false, true) + val `OLDAP-2.4` = spdx("Open LDAP Public License v2.4", "OLDAP-2.4", false, false) + val `OLDAP-2.5` = spdx("Open LDAP Public License v2.5", "OLDAP-2.5", false, false) + val `OLDAP-2.6` = spdx("Open LDAP Public License v2.6", "OLDAP-2.6", false, false) + val `OLDAP-2.7` = spdx("Open LDAP Public License v2.7", "OLDAP-2.7", false, true) + val `OLDAP-2.8` = spdx("Open LDAP Public License v2.8", "OLDAP-2.8", false, false) + val OML = spdx("Open Market License", "OML", false, false) + val OpenSSL = spdx("OpenSSL License", "OpenSSL", false, true) + val `OPL-1.0` = spdx("Open Public License v1.0", "OPL-1.0", false, false) + val `OSET-PL-2.1` = spdx("OSET Public License version 2.1", "OSET-PL-2.1", true, false) + val `OSL-1.0` = spdx("Open Software License 1.0", "OSL-1.0", true, true) + val `OSL-1.1` = spdx("Open Software License 1.1", "OSL-1.1", false, true) + val `OSL-2.0` = spdx("Open Software License 2.0", "OSL-2.0", true, true) + val `OSL-2.1` = spdx("Open Software License 2.1", "OSL-2.1", true, true) + val `OSL-3.0` = spdx("Open Software License 3.0", "OSL-3.0", true, true) + val `PDDL-1.0` = spdx("ODC Public Domain Dedication & License 1.0", "PDDL-1.0", false, false) + val `PHP-3.0` = spdx("PHP License v3.0", "PHP-3.0", true, false) + val `PHP-3.01` = spdx("PHP License v3.01", "PHP-3.01", false, true) + val Plexus = spdx("Plexus Classworlds License", "Plexus", false, false) + val PostgreSQL = spdx("PostgreSQL License", "PostgreSQL", true, false) + val psfrag = spdx("psfrag License", "psfrag", false, false) + val psutils = spdx("psutils License", "psutils", false, false) + val `Python-2.0` = spdx("Python License 2.0", "Python-2.0", true, true) + val Qhull = spdx("Qhull License", "Qhull", false, false) + val `QPL-1.0` = spdx("Q Public License 1.0", "QPL-1.0", true, true) + val Rdisc = spdx("Rdisc License", "Rdisc", false, false) + val `RHeCos-1.1` = spdx("Red Hat eCos Public License v1.1", "RHeCos-1.1", false, false) + val `RPL-1.1` = spdx("Reciprocal Public License 1.1", "RPL-1.1", true, false) + val `RPL-1.5` = spdx("Reciprocal Public License 1.5", "RPL-1.5", true, false) + val `RPSL-1.0` = spdx("RealNetworks Public Source License v1.0", "RPSL-1.0", true, true) + val `RSA-MD` = spdx("RSA Message-Digest License ", "RSA-MD", false, false) + val RSCPL = spdx("Ricoh Source Code Public License", "RSCPL", true, false) + val Ruby = spdx("Ruby License", "Ruby", false, true) + val `SAX-PD` = spdx("Sax Public Domain Notice", "SAX-PD", false, false) + val Saxpath = spdx("Saxpath License", "Saxpath", false, false) + val SCEA = spdx("SCEA Shared Source License", "SCEA", false, false) + val Sendmail = spdx("Sendmail License", "Sendmail", false, false) + val `SGI-B-1.0` = spdx("SGI Free Software License B v1.0", "SGI-B-1.0", false, false) + val `SGI-B-1.1` = spdx("SGI Free Software License B v1.1", "SGI-B-1.1", false, false) + val `SGI-B-2.0` = spdx("SGI Free Software License B v2.0", "SGI-B-2.0", false, true) + val `SimPL-2.0` = spdx("Simple Public License 2.0", "SimPL-2.0", true, false) + val `SISSL-1.2` = spdx("Sun Industry Standards Source License v1.2", "SISSL-1.2", false, false) + val SISSL = spdx("Sun Industry Standards Source License v1.1", "SISSL", true, false) + val Sleepycat = spdx("Sleepycat License", "Sleepycat", true, true) + val SMLNJ = spdx("Standard ML of New Jersey License", "SMLNJ", false, true) + val SMPPL = spdx("Secure Messaging Protocol Public License", "SMPPL", false, false) + val SNIA = spdx("SNIA Public License 1.1", "SNIA", false, false) + val `Spencer-86` = spdx("Spencer License 86", "Spencer-86", false, false) + val `Spencer-94` = spdx("Spencer License 94", "Spencer-94", false, false) + val `Spencer-99` = spdx("Spencer License 99", "Spencer-99", false, false) + val `SPL-1.0` = spdx("Sun Public License v1.0", "SPL-1.0", true, true) + val `SugarCRM-1.1.3` = spdx("SugarCRM Public License v1.1.3", "SugarCRM-1.1.3", false, false) + val SWL = spdx("Scheme Widget Library (SWL) Software License Agreement", "SWL", false, false) + val TCL = spdx("TCL/TK License", "TCL", false, false) + val `TCP-wrappers` = spdx("TCP Wrappers License", "TCP-wrappers", false, false) + val TMate = spdx("TMate Open Source License", "TMate", false, false) + val `TORQUE-1.1` = spdx("TORQUE v2.5+ Software License v1.1", "TORQUE-1.1", false, false) + val TOSL = spdx("Trusster Open Source License", "TOSL", false, false) + val `Unicode-DFS-2015` = spdx("Unicode License Agreement - Data Files and Software (2015)", "Unicode-DFS-2015", false, false) + val `Unicode-DFS-2016` = spdx("Unicode License Agreement - Data Files and Software (2016)", "Unicode-DFS-2016", false, false) + val `Unicode-TOU` = spdx("Unicode Terms of Use", "Unicode-TOU", false, false) + val Unlicense = spdx("The Unlicense", "Unlicense", false, true) + val `UPL-1.0` = spdx("Universal Permissive License v1.0", "UPL-1.0", true, true) + val Vim = spdx("Vim License", "Vim", false, true) + val VOSTROM = spdx("VOSTROM Public License for Open Source", "VOSTROM", false, false) + val `VSL-1.0` = spdx("Vovida Software License v1.0", "VSL-1.0", true, false) + val `W3C-19980720` = spdx("W3C Software Notice and License (1998-07-20)", "W3C-19980720", false, false) + val `W3C-20150513` = spdx("W3C Software Notice and Document License (2015-05-13)", "W3C-20150513", false, false) + val W3C = spdx("W3C Software Notice and License (2002-12-31)", "W3C", true, true) + val `Watcom-1.0` = spdx("Sybase Open Watcom Public License 1.0", "Watcom-1.0", true, false) + val Wsuipa = spdx("Wsuipa License", "Wsuipa", false, false) + val WTFPL = spdx("Do What The F*ck You Want To Public License", "WTFPL", false, true) + val X11 = spdx("X11 License", "X11", false, true) + val Xerox = spdx("Xerox License", "Xerox", false, false) + val `XFree86-1.1` = spdx("XFree86 License 1.1", "XFree86-1.1", false, true) + val xinetd = spdx("xinetd License", "xinetd", false, true) + val Xnet = spdx("X.Net License", "Xnet", true, false) + val xpp = spdx("XPP License", "xpp", false, false) + val XSkat = spdx("XSkat License", "XSkat", false, false) + val `YPL-1.0` = spdx("Yahoo! Public License v1.0", "YPL-1.0", false, false) + val `YPL-1.1` = spdx("Yahoo! Public License v1.1", "YPL-1.1", false, true) + val Zed = spdx("Zed License", "Zed", false, false) + val `Zend-2.0` = spdx("Zend License v2.0", "Zend-2.0", false, true) + val `Zimbra-1.3` = spdx("Zimbra Public License v1.3", "Zimbra-1.3", false, true) + val `Zimbra-1.4` = spdx("Zimbra Public License v1.4", "Zimbra-1.4", false, false) + val `zlib-acknowledgement` = spdx("zlib/libpng License with Acknowledgement", "zlib-acknowledgement", false, false) + val Zlib = spdx("zlib License", "Zlib", true, true) + val `ZPL-1.1` = spdx("Zope Public License 1.1", "ZPL-1.1", false, false) + val `ZPL-2.0` = spdx("Zope Public License 2.0", "ZPL-2.0", true, true) + val `ZPL-2.1` = spdx("Zope Public License 2.1", "ZPL-2.1", false, true) + val `AGPL-3.0` = spdx("GNU Affero General Public License v3.0", "AGPL-3.0", true, false) + val `eCos-2.0` = spdx("eCos license version 2.0", "eCos-2.0", false, false) + val `GFDL-1.1` = spdx("GNU Free Documentation License v1.1", "GFDL-1.1", false, false) + val `GFDL-1.2` = spdx("GNU Free Documentation License v1.2", "GFDL-1.2", false, false) + val `GFDL-1.3` = spdx("GNU Free Documentation License v1.3", "GFDL-1.3", false, false) + val `GPL-1.0+` = spdx("GNU General Public License v1.0 or later", "GPL-1.0+", false, false) + val `GPL-1.0` = spdx("GNU General Public License v1.0 only", "GPL-1.0", false, false) + val `GPL-2.0+` = spdx("GNU General Public License v2.0 or later", "GPL-2.0+", true, false) + val `GPL-2.0-with-autoconf-exception` = spdx("GNU General Public License v2.0 w/Autoconf exception", "GPL-2.0-with-autoconf-exception", false, false) + val `GPL-2.0-with-bison-exception` = spdx("GNU General Public License v2.0 w/Bison exception", "GPL-2.0-with-bison-exception", false, false) + val `GPL-2.0-with-classpath-exception` = spdx("GNU General Public License v2.0 w/Classpath exception", "GPL-2.0-with-classpath-exception", false, false) + val `GPL-2.0-with-font-exception` = spdx("GNU General Public License v2.0 w/Font exception", "GPL-2.0-with-font-exception", false, false) + val `GPL-2.0-with-GCC-exception` = spdx("GNU General Public License v2.0 w/GCC Runtime Library exception", "GPL-2.0-with-GCC-exception", false, false) + val `GPL-2.0` = spdx("GNU General Public License v2.0 only", "GPL-2.0", true, false) + val `GPL-3.0+` = spdx("GNU General Public License v3.0 or later", "GPL-3.0+", true, false) + val `GPL-3.0-with-autoconf-exception` = spdx("GNU General Public License v3.0 w/Autoconf exception", "GPL-3.0-with-autoconf-exception", false, false) + val `GPL-3.0-with-GCC-exception` = spdx("GNU General Public License v3.0 w/GCC Runtime Library exception", "GPL-3.0-with-GCC-exception", true, false) + val `GPL-3.0` = spdx("GNU General Public License v3.0 only", "GPL-3.0", true, false) + val `LGPL-2.0+` = spdx("GNU Library General Public License v2 or later", "LGPL-2.0+", true, false) + val `LGPL-2.0` = spdx("GNU Library General Public License v2 only", "LGPL-2.0", true, false) + val `LGPL-2.1+` = spdx("GNU Library General Public License v2 or later", "LGPL-2.1+", true, false) + val `LGPL-2.1` = spdx("GNU Lesser General Public License v2.1 only", "LGPL-2.1", true, false) + val `LGPL-3.0+` = spdx("GNU Lesser General Public License v3.0 or later", "LGPL-3.0+", true, false) + val `LGPL-3.0` = spdx("GNU Lesser General Public License v3.0 only", "LGPL-3.0", true, false) + val Nunit = spdx("Nunit License", "Nunit", false, false) + val `StandardML-NJ` = spdx("Standard ML of New Jersey License", "StandardML-NJ", false, false) + val wxWindows = spdx("wxWindows Library License", "wxWindows", false, false) + + private def spdx(fullName: String, id: String, isOsiApproved: Boolean, isFsfLibre: Boolean): License = + License(fullName, id, s"https://spdx.org/licenses/$id.html", isOsiApproved, isFsfLibre, "repo") + + val PublicDomain = License( + id = "Public Domain", + name = "Public Domain", + url = "https://creativecommons.org/publicdomain/zero/1.0/", + isOsiApproved = true, // sort of: https://opensource.org/faq#public-domain + isFsfLibre = true, // I'm not sure about this + distribution = "repo" + ) + + val Scala = License( + id = "Scala License", + name = "Scala License", + url = "http://www.scala-lang.org/license.html", + isOsiApproved = false, + isFsfLibre = false, + distribution = "repo" + ) + + val TypesafeSubscriptionAgreement = License( + id = "Typesafe Subscription Agreement", + name = "Typesafe Subscription Agreement", + url = "http://downloads.typesafe.com/website/legal/TypesafeSubscriptionAgreement.pdf", + isOsiApproved = false, + isFsfLibre = false, + distribution = "repo" + ) + + // https://github.com/sbt/sbt/issues/1937#issuecomment-214963983 + object Common { + val Apache2 = License.`Apache-2.0` + val MIT = License.MIT + val BSD4 = License.`BSD-4-Clause` + val Typesafe = License.TypesafeSubscriptionAgreement + val BSD3 = License.`BSD-3-Clause` + } +} \ No newline at end of file diff --git a/scalalib/src/publish/LocalPublisher.scala b/scalalib/src/publish/LocalPublisher.scala new file mode 100644 index 00000000..d9839831 --- /dev/null +++ b/scalalib/src/publish/LocalPublisher.scala @@ -0,0 +1,32 @@ +package mill.scalalib.publish + + +object LocalPublisher { + + private val root: os.Path = os.home / ".ivy2" / "local" + + def publish(jar: os.Path, + sourcesJar: os.Path, + docJar: os.Path, + pom: os.Path, + ivy: os.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", + docJar -> releaseDir / "docs" / s"${artifact.id}-javadoc.jar", + pom -> releaseDir / "poms" / s"${artifact.id}.pom", + ivy -> releaseDir / "ivys" / "ivy.xml" + ) + } + + private def writeFiles(fromTo: (os.Path, os.Path)*): Unit = { + fromTo.foreach { + case (from, to) => + os.makeDir.all(to / os.up) + os.copy.over(from, to) + } + } + +} diff --git a/scalalib/src/publish/Pom.scala b/scalalib/src/publish/Pom.scala new file mode 100644 index 00000000..57a0e196 --- /dev/null +++ b/scalalib/src/publish/Pom.scala @@ -0,0 +1,117 @@ +package mill.scalalib.publish + +import mill.util.Loose.Agg + +import scala.xml.{Atom, Elem, NodeSeq, PrettyPrinter} + +object Pom { + + val head = "\n" + + implicit class XmlOps(val e: Elem) extends AnyVal { + // source: https://stackoverflow.com/a/5254068/449071 + def optional : NodeSeq = { + require(e.child.length == 1) + e.child.head match { + case atom: Atom[Option[_]] => atom.data match { + case None => NodeSeq.Empty + case Some(x) => e.copy(child = x match { + case n: NodeSeq => n + case x => new Atom(x) + }) + } + case _ => e + } + } + } + + //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.versionControl.connection}.optional } + { {pomSettings.versionControl.developerConnection}.optional } + { {pomSettings.versionControl.tag}.optional } + { {pomSettings.versionControl.browsableRepository}.optional } + + + {pomSettings.developers.map(renderDeveloper)} + + + {dependencies.map(renderDependency).toSeq} + + + + 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}.optional } + { {d.organizationUrl}.optional } + + } + + 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 + } + if (d.exclusions.isEmpty) + + {d.artifact.group} + {d.artifact.id} + {d.artifact.version} + {scope} + + else + + {d.artifact.group} + {d.artifact.id} + {d.artifact.version} + + {d.exclusions.map(ex => + + {ex._1} + {ex._2} + + )} + + {scope} + + } + +} diff --git a/scalalib/src/publish/SonatypeHttpApi.scala b/scalalib/src/publish/SonatypeHttpApi.scala new file mode 100644 index 00000000..12defa93 --- /dev/null +++ b/scalalib/src/publish/SonatypeHttpApi.scala @@ -0,0 +1,134 @@ +package mill.scalalib.publish + +import java.util.Base64 + + + +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)) + .throwError + + val resourceUri = + ujson + .read(response.body)("data") + .arr + .find(profile => + groupId.split('.').startsWith(profile("name").str.split('.'))) + .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 + .throwError + + ujson.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}"}}""")) + .throwError + + ujson.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/publish/SonatypePublisher.scala b/scalalib/src/publish/SonatypePublisher.scala new file mode 100644 index 00000000..1843943b --- /dev/null +++ b/scalalib/src/publish/SonatypePublisher.scala @@ -0,0 +1,164 @@ +package mill.scalalib.publish + +import java.math.BigInteger +import java.security.MessageDigest + +import mill.api.Logger + +import scalaj.http.HttpResponse + +class SonatypePublisher(uri: String, + snapshotUri: String, + credentials: String, + gpgPassphrase: Option[String], + signed: Boolean, + log: Logger) { + + private val api = new SonatypeHttpApi(uri, credentials) + + def publish(fileMapping: Seq[(os.Path, String)], artifact: Artifact, release: Boolean): Unit = { + publishAll(release, fileMapping -> artifact) + } + def publishAll(release: Boolean, artifacts: (Seq[(os.Path, String)], Artifact)*): Unit = { + + val mappings = for ((fileMapping0, artifact) <- artifacts) yield { + val publishPath = Seq( + artifact.group.replace(".", "/"), + artifact.id, + artifact.version + ).mkString("/") + val fileMapping = fileMapping0.map{ case (file, name) => (file, publishPath+"/"+name) } + + val signedArtifacts = if (signed) fileMapping.map { + case (file, name) => poorMansSign(file, gpgPassphrase) -> s"$name.asc" + } else Seq() + + artifact -> (fileMapping ++ signedArtifacts).flatMap { + case (file, name) => + val content = os.read.bytes(file) + + Seq( + name -> content, + (name + ".md5") -> md5hex(content), + (name + ".sha1") -> sha1hex(content) + ) + } + } + + val (snapshots, releases) = mappings.partition(_._1.isSnapshot) + if(snapshots.nonEmpty) { + publishSnapshot(snapshots.flatMap(_._2), snapshots.map(_._1)) + } + val releaseGroups = releases.groupBy(_._1.group) + for((group, groupReleases) <- releaseGroups){ + publishRelease(release, groupReleases.flatMap(_._2), group, releases.map(_._1)) + } + } + + private def publishSnapshot(payloads: Seq[(String, Array[Byte])], + artifacts: Seq[Artifact]): Unit = { + + val publishResults = payloads.map { + case (fileName, data) => + log.info(s"Uploading $fileName") + val resp = api.upload(s"$snapshotUri/$fileName", data) + resp + } + reportPublishResults(publishResults, artifacts) + } + + private def publishRelease(release: Boolean, + payloads: Seq[(String, Array[Byte])], + stagingProfile: String, + artifacts: Seq[Artifact]): Unit = { + val profileUri = api.getStagingProfileUri(stagingProfile) + val stagingRepoId = + api.createStagingRepo(profileUri, stagingProfile) + val baseUri = s"$uri/staging/deployByRepositoryId/$stagingRepoId/" + + val publishResults = payloads.map { + case (fileName, data) => + log.info(s"Uploading ${fileName}") + api.upload(s"$baseUri/$fileName", data) + } + reportPublishResults(publishResults, artifacts) + + if (release) { + 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 ${artifacts.map(_.id).mkString(", ")} successfully") + } + } + + private def reportPublishResults(publishResults: Seq[HttpResponse[String]], + artifacts: Seq[Artifact]) = { + if (publishResults.forall(_.is2xx)) { + log.info(s"Published ${artifacts.map(_.id).mkString(", ")} to Sonatype") + } else { + val errors = publishResults.filterNot(_.is2xx).map { response => + s"Code: ${response.code}, message: ${response.body}" + } + throw new RuntimeException( + s"Failed to publish ${artifacts.map(_.id).mkString(", ")} 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: os.Path, maybePassphrase: Option[String]): os.Path = { + val fileName = file.toString + maybePassphrase match { + case Some(passphrase) => + os.proc("gpg", "--passphrase", passphrase, "--batch", "--yes", "-a", "-b", fileName) + .call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit) + case None => + os.proc("gpg", "--batch", "--yes", "-a", "-b", fileName) + .call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit) + } + os.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/publish/VersionControl.scala b/scalalib/src/publish/VersionControl.scala new file mode 100644 index 00000000..aad38ac3 --- /dev/null +++ b/scalalib/src/publish/VersionControl.scala @@ -0,0 +1,131 @@ +package mill.scalalib.publish + +// https://maven.apache.org/pom.html#SCM +/* + * @param browsableRepository: a publicly browsable repository + * (example: https://github.com/lihaoyi/mill) + * @param connection: read-only connection to repository + * (example: scm:git:git://github.com/lihaoyi/mill.git) + * @param developerConnection: read-write connection to repository + * (example: scm:git:git@github.com:lihaoyi/mill.git) + * @param tag: tag that was created for this release. This is useful for + * git and mercurial since it's not possible to include the tag in + * the connection url. + * (example: v2.12.4, HEAD, my-branch, fd8a2567ad32c11bcf8adbaca85bdba72bb4f935, ...) + */ +case class VersionControl( + browsableRepository: Option[String] = None, + connection: Option[String] = None, + developerConnection: Option[String] = None, + tag: Option[String] = None +) + +@deprecated("use VersionControl", "0.1.3") +case class SCM( + url: String, + connection: String +) + +object VersionControl { + def github(owner: String, repo: String, tag: Option[String] = None): VersionControl = + VersionControl( + browsableRepository = Some(s"https://github.com/$owner/$repo"), + connection = Some(VersionControlConnection.gitGit("github.com", s"$owner/$repo.git")), + developerConnection = Some(VersionControlConnection.gitSsh("github.com", s":$owner/$repo.git", username = Some("git"))), + tag = tag + ) + def gitlab(owner: String, repo: String, tag: Option[String] = None): VersionControl = + VersionControl( + browsableRepository = Some(s"https://gitlab.com/$owner/$repo"), + connection = Some(VersionControlConnection.gitGit("gitlab.com", s"$owner/$repo.git")), + developerConnection = Some(VersionControlConnection.gitSsh("gitlab.com", s":$owner/$repo.git", username = Some("git"))), + tag = tag + ) +} + +object VersionControlConnection { + def network(scm: String, + protocol: String, + hostname: String, + path: String, + username: Option[String] = None, + password: Option[String] = None, + port: Option[Int] = None): String = { + val portPart = port.map(":" + _).getOrElse("") + val credentials = + username match { + case Some(user) => + val pass = password.map(":" + _).getOrElse("") + s"${user}${pass}@" + case None => + password match { + case Some(p) => sys.error(s"no username set for password: $p") + case _ => "" + } + } + + val path0 = + if(path.startsWith(":") || path.startsWith("/")) path + else "/" + path + + s"scm:${scm}:${protocol}://${credentials}${hostname}${portPart}${path0}" + } + + def file(scm: String, path: String): String = { + s"scm:$scm:file://$path" + } + + def gitGit(hostname: String, + path: String = "", + port: Option[Int] = None): String = + network("git", "git", hostname, path, port = port) + + def gitHttp(hostname: String, + path: String = "", + port: Option[Int] = None): String = + network("git", "http", hostname, path, port = port) + + def gitHttps(hostname: String, + path: String = "", + port: Option[Int] = None): String = + network("git", "https", hostname, path, port = port) + + def gitSsh(hostname: String, + path: String = "", + username: Option[String] = None, + port: Option[Int] = None): String = + network("git", "ssh", hostname, path, username = username, port = port) + + def gitFile(path: String): String = + file("git", path) + + def svnSsh(hostname: String, + path: String = "", + username: Option[String] = None, + port: Option[Int] = None): String = + network("svn", "svn+ssh", hostname, path, username, None, port) + + def svnHttp(hostname: String, + path: String = "", + username: Option[String] = None, + password: Option[String] = None, + port: Option[Int] = None): String = + network("svn", "http", hostname, path, username, password, port) + + def svnHttps(hostname: String, + path: String = "", + username: Option[String] = None, + password: Option[String] = None, + port: Option[Int] = None): String = + network("svn", "https", hostname, path, username, password, port) + + def svnSvn(hostname: String, + path: String = "", + username: Option[String] = None, + password: Option[String] = None, + port: Option[Int] = None): String = + network("svn", "svn", hostname, path, username, password, port) + + def svnFile(path: String): String = + file("svn", path) +} \ No newline at end of file diff --git a/scalalib/src/publish/package.scala b/scalalib/src/publish/package.scala new file mode 100644 index 00000000..99eeec14 --- /dev/null +++ b/scalalib/src/publish/package.scala @@ -0,0 +1,3 @@ +package mill.scalalib + +package object publish extends JsonFormatters diff --git a/scalalib/src/publish/settings.scala b/scalalib/src/publish/settings.scala new file mode 100644 index 00000000..bca81cf0 --- /dev/null +++ b/scalalib/src/publish/settings.scala @@ -0,0 +1,91 @@ +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 fromDepJava(dep: Dep) = { + assert(dep.cross.isConstant, s"Not a Java dependency: $dep") + fromDep(dep, "", "", "") + } + + def fromDep(dep: Dep, + scalaFull: String, + scalaBin: String, + platformSuffix: String): Dependency = { + val name = dep.artifactName( + binaryVersion = scalaBin, + fullVersion = scalaFull, + platformSuffix = platformSuffix + ) + Dependency( + Artifact( + dep.dep.module.organization, + name, + dep.dep.version + ), + Scope.Compile, + if (dep.dep.configuration == "") None else Some(dep.dep.configuration), + dep.dep.exclusions.toList + ) + } +} + +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, + configuration: Option[String] = None, + exclusions: Seq[(String, String)] = Nil +) + +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], + versionControl: VersionControl, + developers: Seq[Developer] +) + +object PomSettings { + @deprecated("use VersionControl instead of SCM", "0.1.3") + def apply(description: String, + organization: String, + url: String, + licenses: Seq[License], + scm: SCM, + developers: Seq[Developer]): PomSettings = { + PomSettings( + description = description, + organization = organization, + url = url, + licenses = licenses, + versionControl = VersionControl( + browsableRepository = Some(scm.url), + connection = Some(scm.connection), + developerConnection = None, + tag = None + ), + developers = developers + ) + } +} diff --git a/scalalib/src/scalafmt/ScalafmtModule.scala b/scalalib/src/scalafmt/ScalafmtModule.scala new file mode 100644 index 00000000..6a81d975 --- /dev/null +++ b/scalalib/src/scalafmt/ScalafmtModule.scala @@ -0,0 +1,57 @@ +package mill.scalalib.scalafmt + +import mill._ +import mill.define._ +import mill.scalalib._ + +trait ScalafmtModule extends JavaModule { + + def reformat(): Command[Unit] = T.command { + ScalafmtWorkerModule + .worker() + .reformat( + filesToFormat(sources()), + scalafmtConfig().head, + scalafmtDeps().map(_.path) + ) + } + + def scalafmtVersion: T[String] = "1.5.1" + + def scalafmtConfig: Sources = T.sources(os.pwd / ".scalafmt.conf") + + def scalafmtDeps: T[Agg[PathRef]] = T { + Lib.resolveDependencies( + zincWorker.repositories, + Lib.depToDependency(_, "2.12.4"), + Seq(ivy"com.geirsson::scalafmt-cli:${scalafmtVersion()}") + ) + } + + protected def filesToFormat(sources: Seq[PathRef]) = { + for { + pathRef <- sources if os.exists(pathRef.path) + file <- os.walk(pathRef.path) if os.isFile(file) && file.ext == "scala" + } yield PathRef(file) + } + +} + +object ScalafmtModule extends ExternalModule with ScalafmtModule { + + def reformatAll(sources: mill.main.Tasks[Seq[PathRef]]): Command[Unit] = + T.command { + val files = Task.sequence(sources.value)().flatMap(filesToFormat) + ScalafmtWorkerModule + .worker() + .reformat( + files, + scalafmtConfig().head, + scalafmtDeps().map(_.path) + ) + } + + implicit def millScoptTargetReads[T] = new mill.main.Tasks.Scopt[T]() + + lazy val millDiscover = Discover[this.type] +} diff --git a/scalalib/src/scalafmt/ScalafmtWorker.scala b/scalalib/src/scalafmt/ScalafmtWorker.scala new file mode 100644 index 00000000..47d8375f --- /dev/null +++ b/scalalib/src/scalafmt/ScalafmtWorker.scala @@ -0,0 +1,57 @@ +package mill.scalalib.scalafmt + +import mill._ +import mill.define.{Discover, ExternalModule, Worker} +import mill.modules.Jvm +import mill.api.Ctx + +import scala.collection.mutable + +object ScalafmtWorkerModule extends ExternalModule { + def worker: Worker[ScalafmtWorker] = T.worker { new ScalafmtWorker() } + + lazy val millDiscover = Discover[this.type] +} + +private[scalafmt] class ScalafmtWorker { + private val reformatted: mutable.Map[os.Path, Int] = mutable.Map.empty + private var configSig: Int = 0 + + def reformat(input: Seq[PathRef], + scalafmtConfig: PathRef, + scalafmtClasspath: Agg[os.Path])(implicit ctx: Ctx): Unit = { + val toFormat = + if (scalafmtConfig.sig != configSig) input + else + input.filterNot(ref => reformatted.get(ref.path).contains(ref.sig)) + + if (toFormat.nonEmpty) { + ctx.log.info(s"Formatting ${toFormat.size} Scala sources") + reformatAction(toFormat.map(_.path), + scalafmtConfig.path, + scalafmtClasspath) + reformatted ++= toFormat.map { ref => + val updRef = PathRef(ref.path) + updRef.path -> updRef.sig + } + configSig = scalafmtConfig.sig + } else { + ctx.log.info(s"Everything is formatted already") + } + } + + private val cliFlags = Seq("--non-interactive", "--quiet") + + private def reformatAction(toFormat: Seq[os.Path], + config: os.Path, + classpath: Agg[os.Path])(implicit ctx: Ctx) = { + val configFlags = + if (os.exists(config)) Seq("--config", config.toString) else Seq.empty + Jvm.runSubprocess( + "org.scalafmt.cli.Cli", + classpath, + mainArgs = toFormat.map(_.toString) ++ configFlags ++ cliFlags + ) + } + +} diff --git a/scalalib/test/resources/hello-java/app/src/Main.java b/scalalib/test/resources/hello-java/app/src/Main.java new file mode 100644 index 00000000..23ddd679 --- /dev/null +++ b/scalalib/test/resources/hello-java/app/src/Main.java @@ -0,0 +1,10 @@ +package hello; + +public class Main{ + public static String getMessage(String[] args){ + return Core.msg() + " " + args[0]; + } + public static void main(String[] args){ + System.out.println(getMessage(args)); + } +} \ No newline at end of file diff --git a/scalalib/test/resources/hello-java/app/src/hello/Main.java b/scalalib/test/resources/hello-java/app/src/hello/Main.java deleted file mode 100644 index 23ddd679..00000000 --- a/scalalib/test/resources/hello-java/app/src/hello/Main.java +++ /dev/null @@ -1,10 +0,0 @@ -package hello; - -public class Main{ - public static String getMessage(String[] args){ - return Core.msg() + " " + args[0]; - } - public static void main(String[] args){ - System.out.println(getMessage(args)); - } -} \ No newline at end of file diff --git a/scalalib/test/resources/hello-java/app/test/src/MyAppTests.java b/scalalib/test/resources/hello-java/app/test/src/MyAppTests.java new file mode 100644 index 00000000..df0d0351 --- /dev/null +++ b/scalalib/test/resources/hello-java/app/test/src/MyAppTests.java @@ -0,0 +1,18 @@ +package hello; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class MyAppTests { + + @Test + public void coreTest() { + assertEquals(Core.msg(), "Hello World"); + } + + @Test + public void appTest() { + assertEquals(Main.getMessage(new String[]{"lols"}), "Hello World lols"); + } + +} \ No newline at end of file diff --git a/scalalib/test/resources/hello-java/app/test/src/hello/MyAppTests.java b/scalalib/test/resources/hello-java/app/test/src/hello/MyAppTests.java deleted file mode 100644 index df0d0351..00000000 --- a/scalalib/test/resources/hello-java/app/test/src/hello/MyAppTests.java +++ /dev/null @@ -1,18 +0,0 @@ -package hello; - -import static org.junit.Assert.assertEquals; -import org.junit.Test; - -public class MyAppTests { - - @Test - public void coreTest() { - assertEquals(Core.msg(), "Hello World"); - } - - @Test - public void appTest() { - assertEquals(Main.getMessage(new String[]{"lols"}), "Hello World lols"); - } - -} \ No newline at end of file diff --git a/scalalib/test/resources/hello-java/core/src/Core.java b/scalalib/test/resources/hello-java/core/src/Core.java new file mode 100644 index 00000000..3ecb1f61 --- /dev/null +++ b/scalalib/test/resources/hello-java/core/src/Core.java @@ -0,0 +1,7 @@ +package hello; + +public class Core{ + public static String msg(){ + return "Hello World"; + } +} \ No newline at end of file diff --git a/scalalib/test/resources/hello-java/core/src/hello/Core.java b/scalalib/test/resources/hello-java/core/src/hello/Core.java deleted file mode 100644 index 3ecb1f61..00000000 --- a/scalalib/test/resources/hello-java/core/src/hello/Core.java +++ /dev/null @@ -1,7 +0,0 @@ -package hello; - -public class Core{ - public static String msg(){ - return "Hello World"; - } -} \ No newline at end of file diff --git a/scalalib/test/resources/hello-java/core/test/src/MyCoreTests.java b/scalalib/test/resources/hello-java/core/test/src/MyCoreTests.java new file mode 100644 index 00000000..38bebaeb --- /dev/null +++ b/scalalib/test/resources/hello-java/core/test/src/MyCoreTests.java @@ -0,0 +1,15 @@ +package hello; + +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +public class MyCoreTests { + @Test + public void msgTest() { + assertEquals(Core.msg(), "Hello World!!"); + } + @Test + public void lengthTest() { + assertEquals(Core.msg().length(), 11); + } +} \ No newline at end of file diff --git a/scalalib/test/resources/hello-java/core/test/src/hello/MyCoreTests.java b/scalalib/test/resources/hello-java/core/test/src/hello/MyCoreTests.java deleted file mode 100644 index 38bebaeb..00000000 --- a/scalalib/test/resources/hello-java/core/test/src/hello/MyCoreTests.java +++ /dev/null @@ -1,15 +0,0 @@ -package hello; - -import static org.junit.Assert.assertEquals; -import org.junit.Test; - -public class MyCoreTests { - @Test - public void msgTest() { - assertEquals(Core.msg(), "Hello World!!"); - } - @Test - public void lengthTest() { - assertEquals(Core.msg().length(), 11); - } -} \ No newline at end of file diff --git a/scalalib/test/src/GenIdeaTests.scala b/scalalib/test/src/GenIdeaTests.scala new file mode 100644 index 00000000..50db95c0 --- /dev/null +++ b/scalalib/test/src/GenIdeaTests.scala @@ -0,0 +1,62 @@ +package mill.scalalib + +import coursier.Cache +import mill._ +import mill.util.{TestEvaluator, TestUtil} +import utest._ + +object GenIdeaTests extends TestSuite { + + val millSourcePath = os.pwd / 'target / 'workspace / "gen-idea" + + trait HelloWorldModule extends scalalib.ScalaModule { + def scalaVersion = "2.12.4" + def millSourcePath = GenIdeaTests.millSourcePath + object test extends super.Tests { + def testFrameworks = Seq("utest.runner.Framework") + } + } + + object HelloWorld extends TestUtil.BaseModule with HelloWorldModule + + val helloWorldEvaluator = TestEvaluator.static(HelloWorld) + + def tests: Tests = Tests { + 'genIdeaTests - { + val pp = new scala.xml.PrettyPrinter(999, 4) + + val layout = GenIdeaImpl.xmlFileLayout( + helloWorldEvaluator.evaluator, + HelloWorld, + ("JDK_1_8", "1.8 (1)"), fetchMillModules = false) + for((relPath, xml) <- layout){ + os.write.over(millSourcePath/ "generated"/ relPath, pp.format(xml), createFolders = true) + } + + Seq( + "gen-idea/idea_modules/iml" -> + millSourcePath / "generated" / ".idea_modules" /".iml", + "gen-idea/idea_modules/test.iml" -> + millSourcePath / "generated" / ".idea_modules" /"test.iml", + "gen-idea/idea_modules/mill-build.iml" -> + millSourcePath / "generated" / ".idea_modules" /"mill-build.iml", + "gen-idea/idea/libraries/scala-library-2.12.4.jar.xml" -> + millSourcePath / "generated" / ".idea" / "libraries" / "scala-library-2.12.4.jar.xml", + "gen-idea/idea/modules.xml" -> + millSourcePath / "generated" / ".idea" / "modules.xml", + "gen-idea/idea/misc.xml" -> + millSourcePath / "generated" / ".idea" / "misc.xml" + ).foreach { case (resource, generated) => + val resourceString = scala.io.Source.fromResource(resource).getLines().mkString("\n") + val generatedString = normaliseLibraryPaths(os.read(generated)) + + assert(resourceString == generatedString) + } + } + } + + + private def normaliseLibraryPaths(in: String): String = { + in.replaceAll(Cache.default.toPath.toAbsolutePath.toString, "COURSIER_HOME") + } +} diff --git a/scalalib/test/src/HelloJavaTests.scala b/scalalib/test/src/HelloJavaTests.scala new file mode 100644 index 00000000..02c2567f --- /dev/null +++ b/scalalib/test/src/HelloJavaTests.scala @@ -0,0 +1,114 @@ +package mill +package scalalib + + +import mill.api.Result +import mill.util.{TestEvaluator, TestUtil} +import utest._ +import utest.framework.TestPath + + +object HelloJavaTests extends TestSuite { + + object HelloJava extends TestUtil.BaseModule{ + def millSourcePath = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') + trait JUnitTests extends TestModule{ + def testFrameworks = Seq("com.novocode.junit.JUnitFramework") + def ivyDeps = Agg(ivy"com.novocode:junit-interface:0.11") + } + + object core extends JavaModule{ + object test extends Tests with JUnitTests + } + object app extends JavaModule{ + def moduleDeps = Seq(core) + object test extends Tests with JUnitTests + } + } + val resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-java" + + def init()(implicit tp: TestPath) = { + val eval = new TestEvaluator(HelloJava) + os.remove.all(HelloJava.millSourcePath) + os.remove.all(eval.outPath) + os.makeDir.all(HelloJava.millSourcePath / os.up) + os.copy(resourcePath, HelloJava.millSourcePath) + eval + } + def tests: Tests = Tests { + 'compile - { + val eval = init() + + val Right((res1, n1)) = eval.apply(HelloJava.core.compile) + val Right((res2, 0)) = eval.apply(HelloJava.core.compile) + val Right((res3, n2)) = eval.apply(HelloJava.app.compile) + + assert( + res1 == res2, + n1 != 0, + n2 != 0, + os.walk(res1.classes.path).exists(_.last == "Core.class"), + !os.walk(res1.classes.path).exists(_.last == "Main.class"), + os.walk(res3.classes.path).exists(_.last == "Main.class"), + !os.walk(res3.classes.path).exists(_.last == "Core.class") + ) + } + 'docJar - { + val eval = init() + + val Right((ref1, _)) = eval.apply(HelloJava.core.docJar) + val Right((ref2, _)) = eval.apply(HelloJava.app.docJar) + + assert( + os.proc("jar", "tf", ref1.path).call().out.lines.contains("hello/Core.html"), + os.proc("jar", "tf", ref2.path).call().out.lines.contains("hello/Main.html") + ) + } + 'test - { + val eval = init() + + val Left(Result.Failure(ref1, Some(v1))) = eval.apply(HelloJava.core.test.test()) + + assert( + v1._2(0).fullyQualifiedName == "hello.MyCoreTests.lengthTest", + v1._2(0).status == "Success", + v1._2(1).fullyQualifiedName == "hello.MyCoreTests.msgTest", + v1._2(1).status == "Failure" + ) + + val Right((v2, _)) = eval.apply(HelloJava.app.test.test()) + + assert( + v2._2(0).fullyQualifiedName == "hello.MyAppTests.appTest", + v2._2(0).status == "Success", + v2._2(1).fullyQualifiedName == "hello.MyAppTests.coreTest", + v2._2(1).status == "Success" + ) + } + 'failures - { + val eval = init() + + val mainJava = HelloJava.millSourcePath / 'app / 'src / "Main.java" + val coreJava = HelloJava.millSourcePath / 'core / 'src / "Core.java" + + val Right(_) = eval.apply(HelloJava.core.compile) + val Right(_) = eval.apply(HelloJava.app.compile) + + ammonite.ops.write.over(mainJava, ammonite.ops.read(mainJava) + "}") + + val Right(_) = eval.apply(HelloJava.core.compile) + val Left(_) = eval.apply(HelloJava.app.compile) + + ammonite.ops.write.over(coreJava, ammonite.ops.read(coreJava) + "}") + + val Left(_) = eval.apply(HelloJava.core.compile) + val Left(_) = eval.apply(HelloJava.app.compile) + + ammonite.ops.write.over(mainJava, ammonite.ops.read(mainJava).dropRight(1)) + ammonite.ops.write.over(coreJava, ammonite.ops.read(coreJava).dropRight(1)) + + val Right(_) = eval.apply(HelloJava.core.compile) + val Right(_) = eval.apply(HelloJava.app.compile) + } + } +} diff --git a/scalalib/test/src/HelloWorldTests.scala b/scalalib/test/src/HelloWorldTests.scala new file mode 100644 index 00000000..da08f056 --- /dev/null +++ b/scalalib/test/src/HelloWorldTests.scala @@ -0,0 +1,934 @@ +package mill.scalalib + +import java.io.ByteArrayOutputStream +import java.util.jar.JarFile + +import mill._ +import mill.define.Target +import mill.api.Result.Exception +import mill.eval.{Evaluator, Result} +import mill.modules.Assembly +import mill.scalalib.publish._ +import mill.util.{TestEvaluator, TestUtil} +import mill.scalalib.publish.VersionControl +import utest._ +import utest.framework.TestPath + +import scala.collection.JavaConverters._ +import scala.util.Properties.isJavaAtLeast + + +object HelloWorldTests extends TestSuite { + trait HelloBase extends TestUtil.BaseModule{ + def millSourcePath = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') + } + + trait HelloWorldModule extends scalalib.ScalaModule { + def scalaVersion = "2.12.4" + } + + trait HelloWorldModuleWithMain extends HelloWorldModule { + def mainClass = Some("Main") + } + + object HelloWorld extends HelloBase { + object core extends HelloWorldModule + } + object CrossHelloWorld extends HelloBase { + object core extends Cross[HelloWorldCross]("2.10.6", "2.11.11", "2.12.3", "2.12.4", "2.13.0-M3") + class HelloWorldCross(val crossScalaVersion: String) extends CrossScalaModule + } + + object HelloWorldDefaultMain extends HelloBase { + object core extends HelloWorldModule + } + + object HelloWorldWithoutMain extends HelloBase { + object core extends HelloWorldModule{ + def mainClass = None + } + } + + object HelloWorldWithMain extends HelloBase { + object core extends HelloWorldModuleWithMain + } + + val akkaHttpDeps = Agg(ivy"com.typesafe.akka::akka-http:10.0.13") + + object HelloWorldAkkaHttpAppend extends HelloBase { + object core extends HelloWorldModuleWithMain { + def ivyDeps = akkaHttpDeps + + def assemblyRules = Seq(Assembly.Rule.Append("reference.conf")) + } + } + + object HelloWorldAkkaHttpExclude extends HelloBase { + object core extends HelloWorldModuleWithMain { + def ivyDeps = akkaHttpDeps + + def assemblyRules = Seq(Assembly.Rule.Exclude("reference.conf")) + } + } + + object HelloWorldAkkaHttpAppendPattern extends HelloBase { + object core extends HelloWorldModuleWithMain { + def ivyDeps = akkaHttpDeps + + def assemblyRules = Seq(Assembly.Rule.AppendPattern(".*.conf")) + } + } + + object HelloWorldAkkaHttpExcludePattern extends HelloBase { + object core extends HelloWorldModuleWithMain { + def ivyDeps = akkaHttpDeps + + def assemblyRules = Seq(Assembly.Rule.ExcludePattern(".*.conf")) + } + } + + object HelloWorldAkkaHttpNoRules extends HelloBase { + object core extends HelloWorldModuleWithMain { + def ivyDeps = akkaHttpDeps + + def assemblyRules = Seq.empty + } + } + + object HelloWorldMultiAppend extends HelloBase { + object core extends HelloWorldModuleWithMain { + def moduleDeps = Seq(model) + + def assemblyRules = Seq(Assembly.Rule.Append("reference.conf")) + } + object model extends HelloWorldModule + } + + object HelloWorldMultiExclude extends HelloBase { + object core extends HelloWorldModuleWithMain { + def moduleDeps = Seq(model) + + def assemblyRules = Seq(Assembly.Rule.Exclude("reference.conf")) + } + object model extends HelloWorldModule + } + + object HelloWorldMultiAppendPattern extends HelloBase { + object core extends HelloWorldModuleWithMain { + def moduleDeps = Seq(model) + + def assemblyRules = Seq(Assembly.Rule.AppendPattern(".*.conf")) + } + object model extends HelloWorldModule + } + + object HelloWorldMultiExcludePattern extends HelloBase { + object core extends HelloWorldModuleWithMain { + def moduleDeps = Seq(model) + + def assemblyRules = Seq(Assembly.Rule.ExcludePattern(".*.conf")) + } + object model extends HelloWorldModule + } + + object HelloWorldMultiNoRules extends HelloBase { + object core extends HelloWorldModuleWithMain { + def moduleDeps = Seq(model) + + def assemblyRules = Seq.empty + } + object model extends HelloWorldModule + } + + object HelloWorldWarnUnused extends HelloBase { + object core extends HelloWorldModule { + def scalacOptions = T(Seq("-Ywarn-unused")) + } + } + + object HelloWorldFatalWarnings extends HelloBase { + object core extends HelloWorldModule { + def scalacOptions = T(Seq("-Ywarn-unused", "-Xfatal-warnings")) + } + } + + object HelloWorldWithDocVersion extends HelloBase { + object core extends HelloWorldModule { + def scalacOptions = T(Seq("-Ywarn-unused", "-Xfatal-warnings")) + def scalaDocOptions = super.scalaDocOptions() ++ Seq("-doc-version", "1.2.3") + } + } + + object HelloWorldOnlyDocVersion extends HelloBase { + object core extends HelloWorldModule { + def scalacOptions = T(Seq("-Ywarn-unused", "-Xfatal-warnings")) + def scalaDocOptions = T(Seq("-doc-version", "1.2.3")) + } + } + + object HelloWorldDocTitle extends HelloBase { + object core extends HelloWorldModule { + def scalaDocOptions = T(Seq("-doc-title", "Hello World")) + } + } + + object HelloWorldWithPublish extends HelloBase{ + object core extends 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.Common.Apache2), + versionControl = VersionControl.github("lihaoyi", "hello-world-publish"), + developers = + Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi")) + ) + } + } + + object HelloWorldScalaOverride extends HelloBase{ + object core extends HelloWorldModule { + + override def scalaVersion: Target[String] = "2.11.11" + } + } + + object HelloWorldIvyDeps extends HelloBase{ + object moduleA extends HelloWorldModule { + + override def ivyDeps = Agg(ivy"com.lihaoyi::sourcecode:0.1.3") + } + object moduleB extends HelloWorldModule { + override def moduleDeps = Seq(moduleA) + override def ivyDeps = Agg(ivy"com.lihaoyi::sourcecode:0.1.4") + } + } + + object HelloWorldTypeLevel extends HelloBase{ + object foo extends ScalaModule { + def scalaVersion = "2.11.8" + override def scalaOrganization = "org.typelevel" + + def ivyDeps = Agg( + ivy"com.github.julien-truffaut::monocle-macro::1.4.0" + ) + def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ Agg( + ivy"org.scalamacros:::paradise:2.1.0" + ) + def scalaDocPluginIvyDeps = super.scalaDocPluginIvyDeps() ++ Agg( + ivy"com.typesafe.genjavadoc:::genjavadoc-plugin:0.11" + ) + } + } + + object HelloWorldMacros extends HelloBase{ + object core extends ScalaModule { + def scalaVersion = "2.12.4" + + def ivyDeps = Agg( + ivy"com.github.julien-truffaut::monocle-macro::1.4.0" + ) + def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ Agg( + ivy"org.scalamacros:::paradise:2.1.0" + ) + } + } + + object HelloWorldFlags extends HelloBase{ + object core extends ScalaModule { + def scalaVersion = "2.12.4" + + def scalacOptions = super.scalacOptions() ++ Seq( + "-Ypartial-unification" + ) + } + } + + object HelloScalacheck extends HelloBase{ + object foo extends ScalaModule { + def scalaVersion = "2.12.4" + object test extends Tests { + def ivyDeps = Agg(ivy"org.scalacheck::scalacheck:1.13.5") + def testFrameworks = Seq("org.scalacheck.ScalaCheckFramework") + } + } + } + + object HelloDotty extends HelloBase{ + object foo extends ScalaModule { + def scalaVersion = "0.9.0-RC1" + def ivyDeps = Agg(ivy"org.typelevel::cats-core:1.2.0".withDottyCompat(scalaVersion())) + } + } + + val resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world" + + 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 jarEntries(jar: JarFile): Set[String] = { + jar.entries().asScala.map(_.getName).toSet + } + + def readFileFromJar(jar: JarFile, name: String): String = { + val is = jar.getInputStream(jar.getEntry(name)) + val baos = new ByteArrayOutputStream() + os.Internals.transfer(is, baos) + new String(baos.toByteArray) + } + + def compileClassfiles = Seq[os.RelPath]( + "Main.class", + "Main$.class", + "Main0.class", + "Main0$.class", + "Main$delayedInit$body.class", + "Person.class", + "Person$.class" + ) + + def workspaceTest[T](m: TestUtil.BaseModule, resourcePath: os.Path = resourcePath) + (t: TestEvaluator => T) + (implicit tp: TestPath): T = { + val eval = new TestEvaluator(m) + os.remove.all(m.millSourcePath) + os.remove.all(eval.outPath) + os.makeDir.all(m.millSourcePath / os.up) + os.copy(resourcePath, m.millSourcePath) + t(eval) + } + + + + + def tests: Tests = Tests { + 'scalaVersion - { + + 'fromBuild - workspaceTest(HelloWorld){eval => + val Right((result, evalCount)) = eval.apply(HelloWorld.core.scalaVersion) + + assert( + result == "2.12.4", + evalCount > 0 + ) + } + 'override - workspaceTest(HelloWorldScalaOverride){eval => + val Right((result, evalCount)) = eval.apply(HelloWorldScalaOverride.core.scalaVersion) + + assert( + result == "2.11.11", + evalCount > 0 + ) + } + } + + 'scalacOptions - { + 'emptyByDefault - workspaceTest(HelloWorld){eval => + val Right((result, evalCount)) = eval.apply(HelloWorld.core.scalacOptions) + + assert( + result.isEmpty, + evalCount > 0 + ) + } + 'override - workspaceTest(HelloWorldFatalWarnings){ eval => + val Right((result, evalCount)) = eval.apply(HelloWorldFatalWarnings.core.scalacOptions) + + assert( + result == Seq("-Ywarn-unused", "-Xfatal-warnings"), + evalCount > 0 + ) + } + } + + 'scalaDocOptions - { + 'emptyByDefault - workspaceTest(HelloWorld){eval => + val Right((result, evalCount)) = eval.apply(HelloWorld.core.scalaDocOptions) + assert( + result.isEmpty, + evalCount > 0 + ) + } + 'override - workspaceTest(HelloWorldDocTitle){ eval => + val Right((result, evalCount)) = eval.apply(HelloWorldDocTitle.core.scalaDocOptions) + assert( + result == Seq("-doc-title", "Hello World"), + evalCount > 0 + ) + } + 'extend - workspaceTest(HelloWorldWithDocVersion){ eval => + val Right((result, evalCount)) = eval.apply(HelloWorldWithDocVersion.core.scalaDocOptions) + assert( + result == Seq("-Ywarn-unused", "-Xfatal-warnings", "-doc-version", "1.2.3"), + evalCount > 0 + ) + } + // make sure options are passed during ScalaDoc generation + 'docJarWithTitle - workspaceTest( + HelloWorldDocTitle, + resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world" + ){ eval => + val Right((_, evalCount)) = eval.apply(HelloWorldDocTitle.core.docJar) + assert( + evalCount > 0, + os.read(eval.outPath / 'core / 'docJar / 'dest / 'javadoc / "index.html").contains("Hello World") + ) + } + 'docJarWithVersion - workspaceTest( + HelloWorldWithDocVersion, + resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world" + ){ eval => + // scaladoc generation fails because of "-Xfatal-warnings" flag + val Left(Result.Failure("docJar generation failed", None)) = eval.apply(HelloWorldWithDocVersion.core.docJar) + } + 'docJarOnlyVersion - workspaceTest( + HelloWorldOnlyDocVersion, + resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world" + ){ eval => + val Right((_, evalCount)) = eval.apply(HelloWorldOnlyDocVersion.core.docJar) + assert( + evalCount > 0, + os.read(eval.outPath / 'core / 'docJar / 'dest / 'javadoc / "index.html").contains("1.2.3") + ) + } + } + + 'scalacPluginClasspath - { + 'withMacroParadise - workspaceTest(HelloWorldTypeLevel){eval => + val Right((result, evalCount)) = eval.apply(HelloWorldTypeLevel.foo.scalacPluginClasspath) + assert( + result.nonEmpty, + result.exists { pathRef => pathRef.path.segments.contains("scalamacros") }, + evalCount > 0 + ) + } + } + + 'scalaDocPluginClasspath - { + 'extend - workspaceTest(HelloWorldTypeLevel){eval => + val Right((result, evalCount)) = eval.apply(HelloWorldTypeLevel.foo.scalaDocPluginClasspath) + assert( + result.nonEmpty, + result.exists { pathRef => pathRef.path.segments.contains("scalamacros") }, + result.exists { pathRef => pathRef.path.segments.contains("genjavadoc") }, + evalCount > 0 + ) + } + } + + 'compile - { + 'fromScratch - workspaceTest(HelloWorld){eval => + val Right((result, evalCount)) = eval.apply(HelloWorld.core.compile) + + val analysisFile = result.analysisFile + val outputFiles = os.walk(result.classes.path) + val expectedClassfiles = compileClassfiles.map( + eval.outPath / 'core / 'compile / 'dest / 'classes / _ + ) + assert( + result.classes.path == eval.outPath / 'core / 'compile / 'dest / 'classes, + os.exists(analysisFile), + outputFiles.nonEmpty, + outputFiles.forall(expectedClassfiles.contains), + evalCount > 0 + ) + + // don't recompile if nothing changed + val Right((_, unchangedEvalCount)) = eval.apply(HelloWorld.core.compile) + + assert(unchangedEvalCount == 0) + } + 'recompileOnChange - workspaceTest(HelloWorld){eval => + val Right((_, freshCount)) = eval.apply(HelloWorld.core.compile) + assert(freshCount > 0) + + os.write.append(HelloWorld.millSourcePath / 'core / 'src / "Main.scala", "\n") + + val Right((_, incCompileCount)) = eval.apply(HelloWorld.core.compile) + assert(incCompileCount > 0, incCompileCount < freshCount) + } + 'failOnError - workspaceTest(HelloWorld){eval => + os.write.append(HelloWorld.millSourcePath / 'core / 'src / "Main.scala", "val x: ") + + val Left(Result.Failure("Compilation failed", _)) = eval.apply(HelloWorld.core.compile) + + + val paths = Evaluator.resolveDestPaths( + eval.outPath, + HelloWorld.core.compile.ctx.segments + ) + + assert( + os.walk(paths.dest / 'classes).isEmpty, + !os.exists(paths.meta) + ) + // Works when fixed + os.write.over( + HelloWorld.millSourcePath / 'core / 'src / "Main.scala", + os.read(HelloWorld.millSourcePath / 'core / 'src / "Main.scala").dropRight("val x: ".length) + ) + + val Right((result, evalCount)) = eval.apply(HelloWorld.core.compile) + } + 'passScalacOptions - workspaceTest(HelloWorldFatalWarnings){ eval => + // compilation fails because of "-Xfatal-warnings" flag + val Left(Result.Failure("Compilation failed", _)) = eval.apply(HelloWorldFatalWarnings.core.compile) + } + } + + 'runMain - { + 'runMainObject - workspaceTest(HelloWorld){eval => + val runResult = eval.outPath / 'core / 'runMain / 'dest / "hello-mill" + + val Right((_, evalCount)) = eval.apply(HelloWorld.core.runMain("Main", runResult.toString)) + assert(evalCount > 0) + + assert( + os.exists(runResult), + os.read(runResult) == "hello rockjam, your age is: 25" + ) + } + 'runCross - { + def cross(eval: TestEvaluator, v: String, expectedOut: String) { + + val runResult = eval.outPath / "hello-mill" + + val Right((_, evalCount)) = eval.apply( + CrossHelloWorld.core(v).runMain("Shim", runResult.toString) + ) + + assert(evalCount > 0) + + + assert( + os.exists(runResult), + os.read(runResult) == expectedOut + ) + } + 'v210 - TestUtil.disableInJava9OrAbove(workspaceTest(CrossHelloWorld)(cross(_, "2.10.6", "2.10.6 rox"))) + 'v211 - TestUtil.disableInJava9OrAbove(workspaceTest(CrossHelloWorld)(cross(_, "2.11.11", "2.11.11 pwns"))) + 'v2123 - workspaceTest(CrossHelloWorld)(cross(_, "2.12.3", "2.12.3 leet")) + 'v2124 - workspaceTest(CrossHelloWorld)(cross(_, "2.12.4", "2.12.4 leet")) + 'v2130M3 - workspaceTest(CrossHelloWorld)(cross(_, "2.13.0-M3", "2.13.0-M3 idk")) + } + + + 'notRunInvalidMainObject - workspaceTest(HelloWorld){eval => + val Left(Result.Failure("subprocess failed", _)) = eval.apply(HelloWorld.core.runMain("Invalid")) + } + 'notRunWhenCompileFailed - workspaceTest(HelloWorld){eval => + os.write.append(HelloWorld.millSourcePath / 'core / 'src / "Main.scala", "val x: ") + + val Left(Result.Failure("Compilation failed", _)) = eval.apply(HelloWorld.core.runMain("Main")) + + } + } + + 'forkRun - { + 'runIfMainClassProvided - workspaceTest(HelloWorldWithMain){eval => + val runResult = eval.outPath / 'core / 'run / 'dest / "hello-mill" + val Right((_, evalCount)) = eval.apply( + HelloWorldWithMain.core.run(runResult.toString) + ) + + assert(evalCount > 0) + + + assert( + os.exists(runResult), + os.read(runResult) == "hello rockjam, your age is: 25" + ) + } + 'notRunWithoutMainClass - workspaceTest( + HelloWorldWithoutMain, + os.pwd / 'scalalib / 'test / 'resources / "hello-world-no-main" + ){eval => + val Left(Result.Failure(_, None)) = eval.apply(HelloWorldWithoutMain.core.run()) + } + + 'runDiscoverMainClass - workspaceTest(HelloWorldWithoutMain){eval => + // Make sure even if there isn't a main class defined explicitly, it gets + // discovered by Zinc and used + val runResult = eval.outPath / 'core / 'run / 'dest / "hello-mill" + val Right((_, evalCount)) = eval.apply( + HelloWorldWithoutMain.core.run(runResult.toString) + ) + + assert(evalCount > 0) + + + assert( + os.exists(runResult), + os.read(runResult) == "hello rockjam, your age is: 25" + ) + } + } + + 'run - { + 'runIfMainClassProvided - workspaceTest(HelloWorldWithMain){eval => + val runResult = eval.outPath / 'core / 'run / 'dest / "hello-mill" + val Right((_, evalCount)) = eval.apply( + HelloWorldWithMain.core.runLocal(runResult.toString) + ) + + assert(evalCount > 0) + + + assert( + os.exists(runResult), + os.read(runResult) == "hello rockjam, your age is: 25" + ) + } + 'runWithDefaultMain - workspaceTest(HelloWorldDefaultMain){eval => + val runResult = eval.outPath / 'core / 'run / 'dest / "hello-mill" + val Right((_, evalCount)) = eval.apply( + HelloWorldDefaultMain.core.runLocal(runResult.toString) + ) + + assert(evalCount > 0) + + + assert( + os.exists(runResult), + os.read(runResult) == "hello rockjam, your age is: 25" + ) + } + 'notRunWithoutMainClass - workspaceTest( + HelloWorldWithoutMain, + os.pwd / 'scalalib / 'test / 'resources / "hello-world-no-main" + ){eval => + val Left(Result.Failure(_, None)) = eval.apply(HelloWorldWithoutMain.core.runLocal()) + + } + } + + 'jar - { + 'nonEmpty - workspaceTest(HelloWorldWithMain){eval => + val Right((result, evalCount)) = eval.apply(HelloWorldWithMain.core.jar) + + assert( + os.exists(result.path), + evalCount > 0 + ) + + val jarFile = new JarFile(result.path.toIO) + val entries = jarFile.entries().asScala.map(_.getName).toSet + + val otherFiles = Seq[os.RelPath]( + os.rel / "META-INF" / "MANIFEST.MF", + "reference.conf" + ) + val expectedFiles = compileClassfiles ++ otherFiles + + assert( + entries.nonEmpty, + entries == expectedFiles.map(_.toString()).toSet + ) + + val mainClass = jarMainClass(jarFile) + assert(mainClass.contains("Main")) + } + + 'logOutputToFile - workspaceTest(HelloWorld){eval => + val outPath = eval.outPath + eval.apply(HelloWorld.core.compile) + + val logFile = outPath / 'core / 'compile / 'log + assert(os.exists(logFile)) + } + } + + 'assembly - { + 'assembly - workspaceTest(HelloWorldWithMain){ eval => + val Right((result, evalCount)) = eval.apply(HelloWorldWithMain.core.assembly) + assert( + os.exists(result.path), + evalCount > 0 + ) + val jarFile = new JarFile(result.path.toIO) + val entries = jarEntries(jarFile) + + val mainPresent = entries.contains("Main.class") + assert(mainPresent) + assert(entries.exists(s => s.contains("scala/Predef.class"))) + + val mainClass = jarMainClass(jarFile) + assert(mainClass.contains("Main")) + } + + 'assemblyRules - { + def checkAppend[M <: TestUtil.BaseModule](module: M, + target: Target[PathRef]) = + workspaceTest(module) { eval => + val Right((result, _)) = eval.apply(target) + + val jarFile = new JarFile(result.path.toIO) + + assert(jarEntries(jarFile).contains("reference.conf")) + + val referenceContent = readFileFromJar(jarFile, "reference.conf") + + assert( + // akka modules configs are present + referenceContent.contains("akka-http Reference Config File"), + referenceContent.contains("akka-http-core Reference Config File"), + referenceContent.contains("Akka Actor Reference Config File"), + referenceContent.contains("Akka Stream Reference Config File"), + // our application config is present too + referenceContent.contains("My application Reference Config File"), + referenceContent.contains( + """akka.http.client.user-agent-header="hello-world-client"""" + ) + ) + } + + val helloWorldMultiResourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-multi" + + def checkAppendMulti[M <: TestUtil.BaseModule]( + module: M, + target: Target[PathRef]) = + workspaceTest( + module, + resourcePath = helloWorldMultiResourcePath + ) { eval => + val Right((result, _)) = eval.apply(target) + + val jarFile = new JarFile(result.path.toIO) + + assert(jarEntries(jarFile).contains("reference.conf")) + + val referenceContent = readFileFromJar(jarFile, "reference.conf") + + assert( + // reference config from core module + referenceContent.contains("Core Reference Config File"), + // reference config from model module + referenceContent.contains("Model Reference Config File"), + // concatenated content + referenceContent.contains("bar.baz=hello"), + referenceContent.contains("foo.bar=2") + ) + } + + 'appendWithDeps - checkAppend( + HelloWorldAkkaHttpAppend, + HelloWorldAkkaHttpAppend.core.assembly + ) + 'appendMultiModule - checkAppendMulti( + HelloWorldMultiAppend, + HelloWorldMultiAppend.core.assembly + ) + 'appendPatternWithDeps - checkAppend( + HelloWorldAkkaHttpAppendPattern, + HelloWorldAkkaHttpAppendPattern.core.assembly + ) + 'appendPatternMultiModule - checkAppendMulti( + HelloWorldMultiAppendPattern, + HelloWorldMultiAppendPattern.core.assembly + ) + + def checkExclude[M <: TestUtil.BaseModule](module: M, + target: Target[PathRef], + resourcePath: os.Path = resourcePath + ) = + workspaceTest(module, resourcePath) { eval => + val Right((result, _)) = eval.apply(target) + + val jarFile = new JarFile(result.path.toIO) + + assert(!jarEntries(jarFile).contains("reference.conf")) + } + + 'excludeWithDeps - checkExclude( + HelloWorldAkkaHttpExclude, + HelloWorldAkkaHttpExclude.core.assembly + ) + 'excludeMultiModule - checkExclude( + HelloWorldMultiExclude, + HelloWorldMultiExclude.core.assembly, + resourcePath = helloWorldMultiResourcePath + + ) + 'excludePatternWithDeps - checkExclude( + HelloWorldAkkaHttpExcludePattern, + HelloWorldAkkaHttpExcludePattern.core.assembly + ) + 'excludePatternMultiModule - checkExclude( + HelloWorldMultiExcludePattern, + HelloWorldMultiExcludePattern.core.assembly, + resourcePath = helloWorldMultiResourcePath + ) + + 'writeFirstWhenNoRule - { + 'withDeps - workspaceTest(HelloWorldAkkaHttpNoRules) { eval => + val Right((result, _)) = eval.apply(HelloWorldAkkaHttpNoRules.core.assembly) + + val jarFile = new JarFile(result.path.toIO) + + assert(jarEntries(jarFile).contains("reference.conf")) + + val referenceContent = readFileFromJar(jarFile, "reference.conf") + + val allOccurrences = Seq( + referenceContent.contains("akka-http Reference Config File"), + referenceContent.contains("akka-http-core Reference Config File"), + referenceContent.contains("Akka Actor Reference Config File"), + referenceContent.contains("Akka Stream Reference Config File"), + referenceContent.contains("My application Reference Config File") + ) + + val timesOcccurres = allOccurrences.find(identity).size + + assert(timesOcccurres == 1) + } + + 'multiModule - workspaceTest( + HelloWorldMultiNoRules, + resourcePath = helloWorldMultiResourcePath + ) { eval => + val Right((result, _)) = eval.apply(HelloWorldMultiNoRules.core.assembly) + + val jarFile = new JarFile(result.path.toIO) + + assert(jarEntries(jarFile).contains("reference.conf")) + + val referenceContent = readFileFromJar(jarFile, "reference.conf") + + assert( + referenceContent.contains("Model Reference Config File"), + referenceContent.contains("foo.bar=2"), + + !referenceContent.contains("Core Reference Config File"), + !referenceContent.contains("bar.baz=hello") + ) + } + } + } + + 'run - workspaceTest(HelloWorldWithMain){eval => + val Right((result, evalCount)) = eval.apply(HelloWorldWithMain.core.assembly) + + assert( + os.exists(result.path), + evalCount > 0 + ) + val runResult = eval.outPath / "hello-mill" + + os.proc("java", "-jar", result.path, runResult).call(cwd = eval.outPath) + + assert( + os.exists(runResult), + os.read(runResult) == "hello rockjam, your age is: 25" + ) + } + } + + 'ivyDeps - workspaceTest(HelloWorldIvyDeps){ eval => + val Right((result, _)) = eval.apply(HelloWorldIvyDeps.moduleA.runClasspath) + assert( + result.exists(_.path.last == "sourcecode_2.12-0.1.3.jar"), + !result.exists(_.path.last == "sourcecode_2.12-0.1.4.jar") + ) + + val Right((result2, _)) = eval.apply(HelloWorldIvyDeps.moduleB.runClasspath) + assert( + result2.exists(_.path.last == "sourcecode_2.12-0.1.4.jar"), + !result2.exists(_.path.last == "sourcecode_2.12-0.1.3.jar") + ) + } + + 'typeLevel - workspaceTest(HelloWorldTypeLevel){ eval => + val classPathsToCheck = Seq( + HelloWorldTypeLevel.foo.runClasspath, + HelloWorldTypeLevel.foo.ammoniteReplClasspath, + HelloWorldTypeLevel.foo.compileClasspath + ) + for(cp <- classPathsToCheck){ + val Right((result, _)) = eval.apply(cp) + assert( + // Make sure every relevant piece org.scala-lang has been substituted for org.typelevel + !result.map(_.toString).exists(x => + x.contains("scala-lang") && + (x.contains("scala-library") || x.contains("scala-compiler") || x.contains("scala-reflect")) + ), + result.map(_.toString).exists(x => x.contains("typelevel") && x.contains("scala-library")) + + ) + } + } + + 'macros - { + // make sure macros are applied when compiling/running + 'runMain - workspaceTest( + HelloWorldMacros, + resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-macros" + ){ eval => + val Right((_, evalCount)) = eval.apply(HelloWorldMacros.core.runMain("Main")) + assert(evalCount > 0) + } + // make sure macros are applied when compiling during scaladoc generation + 'docJar - workspaceTest( + HelloWorldMacros, + resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-macros" + ){ eval => + val Right((_, evalCount)) = eval.apply(HelloWorldMacros.core.docJar) + assert(evalCount > 0) + } + } + + 'flags - { + // make sure flags are passed when compiling/running + 'runMain - workspaceTest( + HelloWorldFlags, + resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-flags" + ){ eval => + val Right((_, evalCount)) = eval.apply(HelloWorldFlags.core.runMain("Main")) + assert(evalCount > 0) + } + // make sure flags are passed during ScalaDoc generation + 'docJar - workspaceTest( + HelloWorldFlags, + resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-flags" + ){ eval => + val Right((_, evalCount)) = eval.apply(HelloWorldFlags.core.docJar) + assert(evalCount > 0) + } + } + + 'scalacheck - workspaceTest( + HelloScalacheck, + resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-scalacheck" + ){ eval => + val Right((res, evalCount)) = eval.apply(HelloScalacheck.foo.test.test()) + assert( + evalCount > 0, + res._2.map(_.selector) == Seq( + "String.startsWith", + "String.endsWith", + "String.substring", + "String.substring" + ) + ) + } + + 'dotty - workspaceTest( + HelloDotty, + resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-dotty" + ){ eval => + if (isJavaAtLeast("9")) { + // Skip the test because Dotty does not support Java >= 9 yet + // (see https://github.com/lampepfl/dotty/pull/3138) + } else { + val Right((_, evalCount)) = eval.apply(HelloDotty.foo.run()) + assert(evalCount > 0) + } + } + } +} diff --git a/scalalib/test/src/ResolveDepsTests.scala b/scalalib/test/src/ResolveDepsTests.scala new file mode 100644 index 00000000..78361625 --- /dev/null +++ b/scalalib/test/src/ResolveDepsTests.scala @@ -0,0 +1,77 @@ +package mill.scalalib + +import coursier.Cache +import coursier.maven.MavenRepository +import mill.api.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, + Lib.depToDependency(_, "2.12.4", ""), + deps + ) + + val tests = Tests { + 'resolveValidDeps - { + val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3") + val Success(paths) = evalDeps(deps) + assert(paths.nonEmpty) + } + + 'resolveValidDepsWithClassifier - { + val deps = Agg(ivy"org.lwjgl:lwjgl:3.1.1;classifier=natives-macos") + val Success(paths) = evalDeps(deps) + assert(paths.nonEmpty) + assert(paths.items.next.path.toString.contains("natives-macos")) + } + + 'resolveTransitiveRuntimeDeps - { + val deps = Agg(ivy"org.mockito:mockito-core:2.7.22") + val Success(paths) = evalDeps(deps) + assert(paths.nonEmpty) + assert(paths.exists(_.path.toString.contains("objenesis"))) + assert(paths.exists(_.path.toString.contains("byte-buddy"))) + } + + 'excludeTransitiveDeps - { + val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".exclude("com.lihaoyi" -> "fansi_2.12")) + val Success(paths) = evalDeps(deps) + assert(!paths.exists(_.path.toString.contains("fansi_2.12"))) + } + + 'excludeTransitiveDepsByOrg - { + val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".excludeOrg("com.lihaoyi")) + val Success(paths) = evalDeps(deps) + assert(!paths.exists(path => path.path.toString.contains("com/lihaoyi") && !path.path.toString.contains("pprint_2.12"))) + } + + 'excludeTransitiveDepsByName - { + val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".excludeName("fansi_2.12")) + val Success(paths) = evalDeps(deps) + assert(!paths.exists(_.path.toString.contains("fansi_2.12"))) + } + + '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/src/VersionControlTests.scala b/scalalib/test/src/VersionControlTests.scala new file mode 100644 index 00000000..fafdca2d --- /dev/null +++ b/scalalib/test/src/VersionControlTests.scala @@ -0,0 +1,74 @@ +package mill.scalalib + +import mill.scalalib.publish.{VersionControl, VersionControlConnection} + +import utest._ + +object VersionContolTests extends TestSuite { + + import VersionControl._ + import VersionControlConnection._ + + val tests = Tests { + 'github - { + assert( + github("lihaoyi", "mill") == + VersionControl( + browsableRepository = Some("https://github.com/lihaoyi/mill"), + connection = Some("scm:git:git://github.com/lihaoyi/mill.git"), + developerConnection = Some("scm:git:ssh://git@github.com:lihaoyi/mill.git"), + tag = None + ) + ) + } + 'git - { + assert( + gitGit("example.org", "path.git", port = Some(9418)) == + "scm:git:git://example.org:9418/path.git" + ) + + assert( + gitHttp("example.org") == + "scm:git:http://example.org/" + ) + + assert( + gitHttps("example.org", "path.git") == + "scm:git:https://example.org/path.git" + ) + + assert( + gitSsh("example.org", "path.git") == + "scm:git:ssh://example.org/path.git" + ) + + assert( + gitFile("/home/gui/repos/foo/bare.git") == + "scm:git:file:///home/gui/repos/foo/bare.git" + ) + + } + 'svn - { + assert( + svnSsh("example.org", "repo") == + "scm:svn:svn+ssh://example.org/repo" + ) + assert( + svnHttp("example.org", "repo", Some("user"), Some("pass")) == + "scm:svn:http://user:pass@example.org/repo" + ) + assert( + svnHttps("example.org", "repo", Some("user")) == + "scm:svn:https://user@example.org/repo" + ) + assert( + svnSvn("example.org", "repo", port = Some(3690)) == + "scm:svn:svn://example.org:3690/repo" + ) + assert( + svnFile("/var/svn/repo") == + "scm:svn:file:///var/svn/repo" + ) + } + } +} \ No newline at end of file diff --git a/scalalib/test/src/dependency/metadata/MetadataLoaderFactoryTests.scala b/scalalib/test/src/dependency/metadata/MetadataLoaderFactoryTests.scala new file mode 100644 index 00000000..4c2206b8 --- /dev/null +++ b/scalalib/test/src/dependency/metadata/MetadataLoaderFactoryTests.scala @@ -0,0 +1,64 @@ +/* + * This file contains code originally published under the following license: + * + * Copyright (c) 2012, Roman Timushev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package mill.scalalib.dependency.metadata + +import coursier.Fetch.Content +import coursier.core.{Artifact, Module, Project, Repository} +import coursier.ivy.IvyRepository +import coursier.maven.MavenRepository +import coursier.util.{EitherT, Monad} +import utest._ + +object MetadataLoaderFactoryTests extends TestSuite { + + val tests = Tests { + 'mavenRepository - { + val mavenRepo = MavenRepository("https://repo1.maven.org/maven2") + assertMatch(MetadataLoaderFactory(mavenRepo)) { + case Some(MavenMetadataLoader(`mavenRepo`)) => + } + } + 'ivyRepository - { + val Right(ivyRepo) = IvyRepository.parse( + "https://dl.bintray.com/sbt/sbt-plugin-releases/" + coursier.ivy.Pattern.default.string, + dropInfoAttributes = true) + assertMatch(MetadataLoaderFactory(ivyRepo)) { case None => } + } + 'otherRepository - { + val otherRepo = new CustomRepository + assertMatch(MetadataLoaderFactory(otherRepo)) { case None => } + } + } + + case class CustomRepository() extends Repository { + override def find[F[_]](module: Module, version: String, fetch: Content[F])( + implicit F: Monad[F]): EitherT[F, String, (Artifact.Source, Project)] = + ??? + } +} diff --git a/scalalib/test/src/dependency/updates/UpdatesFinderTests.scala b/scalalib/test/src/dependency/updates/UpdatesFinderTests.scala new file mode 100644 index 00000000..7b6e6e36 --- /dev/null +++ b/scalalib/test/src/dependency/updates/UpdatesFinderTests.scala @@ -0,0 +1,173 @@ +/* + * This file contains code originally published under the following license: + * + * Copyright (c) 2012, Roman Timushev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package mill.scalalib.dependency.updates + +import mill.scalalib.dependency.versions.{DependencyVersions, Version} +import utest._ + +object UpdatesFinderTests extends TestSuite { + + private def updates(current: String, + available: Seq[String], + allowPreRelease: Boolean) = { + val dependency = coursier.Dependency( + coursier.Module("com.example.organization", "example-artifact"), + current) + val currentVersion = Version(current) + val allVersions = available.map(Version(_)).toSet + + UpdatesFinder + .findUpdates(DependencyVersions(dependency, currentVersion, allVersions), + allowPreRelease) + .updates + .map(_.toString) + } + + val available = Seq( + "0.9.9-SNAPSHOT", + "0.9.9-M3", + "0.9.9", + "1.0.0-SNAPSHOT", + "1.0.0-M2", + "1.0.0-M3", + "1.0.0", + "1.0.1-SNAPSHOT", + "1.0.1-M3", + "1.0.1" + ) + + val tests = Tests { + + 'snapshotArtifacts - { + val u = updates("1.0.0-SNAPSHOT", available, allowPreRelease = false) + val pu = updates("1.0.0-SNAPSHOT", available, allowPreRelease = true) + + 'noOldStableVersions - { + assert(!u.contains("0.9.9")) + } + 'noOldMilestones - { + assert(!u.contains("0.9.9-M3")) + } + 'noOldSnapshots - { + assert(!u.contains("0.9.9-SNAPSHOT")) + } + 'noCurrentMilestones - { + assert(!u.contains("1.0.0-M3")) + } + 'noCurrentSnapshot - { + assert(!u.contains("1.0.0-SNAPSHOT")) + } + 'stableUpdates - { + assert(u.contains("1.0.0") && u.contains("1.0.1")) + } + 'milestoneUpdates - { + assert(u.contains("1.0.1-M3")) + } + 'snapshotUpdates - { + assert(u.contains("1.0.1-SNAPSHOT")) + } + 'noDifferencesRegardingOptionalPreReleases - { + assert(u == pu) + } + } + + 'milestoneArtifacts - { + val u = updates("1.0.0-M2", available, allowPreRelease = false) + val pu = updates("1.0.0-M2", available, allowPreRelease = true) + + 'noOldStableVersions - { + assert(!u.contains("0.9.9")) + } + 'noOldSnapshots - { + assert(!u.contains("0.9.9-SNAPSHOT")) + } + 'noOldMilestones - { + assert(!u.contains("0.9.9-M3")) + } + 'noCurrentSnapshot - { + assert(!u.contains("1.0.0-SNAPSHOT")) + } + 'currentMilestones - { + assert(u.contains("1.0.0-M3")) + } + 'stableUpdates - { + assert(u.contains("1.0.1")) + } + 'noSnapshotUpdates - { + assert(!u.contains("1.0.1-SNAPSHOT")) + } + 'milestoneUpdates - { + assert(u.contains("1.0.1-M3")) + } + 'noDifferencesRegardingOptionalPreReleases - { + assert(u == pu) + } + } + + 'stableArtifacts - { + val u = updates("1.0.0", available, allowPreRelease = false) + val pu = updates("1.0.0", available, allowPreRelease = true) + + 'noOldStableVersions - { + assert(!u.contains("0.9.9")) + assert(!pu.contains("0.9.9")) + } + 'noOldSnapshots - { + assert(!u.contains("0.9.9-SNAPSHOT")) + assert(!pu.contains("0.9.9-SNAPSHOT")) + } + 'noOldMilestones - { + assert(!u.contains("0.9.9-M3")) + assert(!pu.contains("0.9.9-M3")) + } + 'noCurrentSnapshot - { + assert(!u.contains("1.0.0-SNAPSHOT")) + assert(!pu.contains("1.0.0-SNAPSHOT")) + } + 'noCurrentMilestones - { + assert(!u.contains("1.0.0-M3")) + assert(!pu.contains("1.0.0-M3")) + } + 'stableUpdates - { + assert(u.contains("1.0.1")) + assert(pu.contains("1.0.1")) + } + 'noSnapshotUpdates - { + assert(!u.contains("1.0.1-SNAPSHOT")) + assert(!pu.contains("1.0.1-SNAPSHOT")) + } + 'noMilestoneUpdates - { + assert(!u.contains("1.0.1-M3")) + } + 'milestoneUpdatesWhenAllowingPreReleases - { + assert(pu.contains("1.0.1-M3")) + } + } + } +} diff --git a/scalalib/test/src/dependency/versions/VersionTests.scala b/scalalib/test/src/dependency/versions/VersionTests.scala new file mode 100644 index 00000000..b916c86f --- /dev/null +++ b/scalalib/test/src/dependency/versions/VersionTests.scala @@ -0,0 +1,138 @@ +/* + * This file contains code originally published under the following license: + * + * Copyright (c) 2012, Roman Timushev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package mill.scalalib.dependency.versions + +import utest._ +import fastparse.Parsed + +object VersionTests extends TestSuite { + + val tests = Tests { + 'versionsClassification - { + 'ReleaseVersion - { + List("1.0.0", "1.0.0.Final", "1.0.0-FINAL", "1.0.0.RELEASE") foreach { + rel => + assertMatch(Version(rel)) { + case ReleaseVersion(List(1, 0, 0)) => + } + } + } + 'PreReleaseVersion - { + assertMatch(Version("1.0.0-alpha.1")) { + case PreReleaseVersion(List(1, 0, 0), List("alpha", "1")) => + } + } + 'PreReleaseBuildVersion - { + assertMatch(Version("1.0.0-alpha.1+build.10")) { + case PreReleaseBuildVersion(List(1, 0, 0), + List("alpha", "1"), + List("build", "10")) => + } + } + 'BuildVersion - { + assertMatch(Version("1.0.0+build.10")) { + case BuildVersion(List(1, 0, 0), List("build", "10")) => + } + } + } + + 'semverVersionsOrdering - { + import scala.Ordered._ + + val v = List( + "invalid", + "1.0.0-20131213005945", + "1.0.0-alpha", + "1.0.0-alpha.1", + "1.0.0-beta.2", + "1.0.0-beta.11", + "1.0.0-rc.1", + "1.0.0-rc.1+build.1", + "1.0.0", + "1.0.0+0.3.7", + "1.33.7+build", + "1.33.7+build.2.b8f12d7", + "1.33.7+build.11.e0f985a", + "2.0.M5b", + "2.0.M6-SNAP9", + "2.0.M6-SNAP23", + "2.0.M6-SNAP23a" + ).map(Version.apply) + val pairs = v.tails.flatMap { + case h :: t => t.map((h, _)) + case Nil => List.empty + } + pairs.foreach { + case (a, b) => + assert(a < b) + assert(b > a) + } + } + + 'parser - { + + Symbol("parse 1.0.5") - { + assertMatch(VersionParser.parse("1.0.5")) { + case Parsed.Success((Seq(1, 0, 5), Seq(), Seq()), _) => + } + } + + Symbol("parse 1.0.M3") - { + assertMatch(VersionParser.parse("1.0.M3")) { + case Parsed.Success((Seq(1, 0), Seq("M3"), Seq()), _) => + } + } + Symbol("parse 1.0.3m") - { + assertMatch(VersionParser.parse("1.0.3m")) { + case Parsed.Success((Seq(1, 0), Seq("3m"), Seq()), _) => + } + } + Symbol("parse 1.0.3m.4") - { + assertMatch(VersionParser.parse("1.0.3m.4")) { + case Parsed.Success((Seq(1, 0), Seq("3m", "4"), Seq()), _) => + } + } + Symbol("parse 9.1-901-1.jdbc4") - { + assertMatch(VersionParser.parse("9.1-901-1.jdbc4")) { + case Parsed.Success((Seq(9, 1), Seq("901", "1", "jdbc4"), Seq()), _) => + } + } + Symbol("parse 1.33.7+build/11.e0f985a") - { + assertMatch(VersionParser.parse("1.33.7+build/11.e0f985a")) { + case Parsed.Success((Seq(1, 33, 7), Seq(), Seq("build/11", "e0f985a")), _) => + } + } + Symbol("parse 9.1-901-1.jdbc4+build/11.e0f985a") - { + assertMatch(VersionParser.parse("9.1-901-1.jdbc4+build/11.e0f985a")) { + case Parsed.Success((Seq(9, 1), Seq("901", "1", "jdbc4"), Seq("build/11", "e0f985a")), _) => + } + } + } + } +} diff --git a/scalalib/test/src/mill/scalalib/GenIdeaTests.scala b/scalalib/test/src/mill/scalalib/GenIdeaTests.scala deleted file mode 100644 index 50db95c0..00000000 --- a/scalalib/test/src/mill/scalalib/GenIdeaTests.scala +++ /dev/null @@ -1,62 +0,0 @@ -package mill.scalalib - -import coursier.Cache -import mill._ -import mill.util.{TestEvaluator, TestUtil} -import utest._ - -object GenIdeaTests extends TestSuite { - - val millSourcePath = os.pwd / 'target / 'workspace / "gen-idea" - - trait HelloWorldModule extends scalalib.ScalaModule { - def scalaVersion = "2.12.4" - def millSourcePath = GenIdeaTests.millSourcePath - object test extends super.Tests { - def testFrameworks = Seq("utest.runner.Framework") - } - } - - object HelloWorld extends TestUtil.BaseModule with HelloWorldModule - - val helloWorldEvaluator = TestEvaluator.static(HelloWorld) - - def tests: Tests = Tests { - 'genIdeaTests - { - val pp = new scala.xml.PrettyPrinter(999, 4) - - val layout = GenIdeaImpl.xmlFileLayout( - helloWorldEvaluator.evaluator, - HelloWorld, - ("JDK_1_8", "1.8 (1)"), fetchMillModules = false) - for((relPath, xml) <- layout){ - os.write.over(millSourcePath/ "generated"/ relPath, pp.format(xml), createFolders = true) - } - - Seq( - "gen-idea/idea_modules/iml" -> - millSourcePath / "generated" / ".idea_modules" /".iml", - "gen-idea/idea_modules/test.iml" -> - millSourcePath / "generated" / ".idea_modules" /"test.iml", - "gen-idea/idea_modules/mill-build.iml" -> - millSourcePath / "generated" / ".idea_modules" /"mill-build.iml", - "gen-idea/idea/libraries/scala-library-2.12.4.jar.xml" -> - millSourcePath / "generated" / ".idea" / "libraries" / "scala-library-2.12.4.jar.xml", - "gen-idea/idea/modules.xml" -> - millSourcePath / "generated" / ".idea" / "modules.xml", - "gen-idea/idea/misc.xml" -> - millSourcePath / "generated" / ".idea" / "misc.xml" - ).foreach { case (resource, generated) => - val resourceString = scala.io.Source.fromResource(resource).getLines().mkString("\n") - val generatedString = normaliseLibraryPaths(os.read(generated)) - - assert(resourceString == generatedString) - } - } - } - - - private def normaliseLibraryPaths(in: String): String = { - in.replaceAll(Cache.default.toPath.toAbsolutePath.toString, "COURSIER_HOME") - } -} diff --git a/scalalib/test/src/mill/scalalib/HelloJavaTests.scala b/scalalib/test/src/mill/scalalib/HelloJavaTests.scala deleted file mode 100644 index 5b7b93b2..00000000 --- a/scalalib/test/src/mill/scalalib/HelloJavaTests.scala +++ /dev/null @@ -1,114 +0,0 @@ -package mill -package scalalib - - -import mill.api.Result -import mill.util.{TestEvaluator, TestUtil} -import utest._ -import utest.framework.TestPath - - -object HelloJavaTests extends TestSuite { - - object HelloJava extends TestUtil.BaseModule{ - def millSourcePath = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') - trait JUnitTests extends TestModule{ - def testFrameworks = Seq("com.novocode.junit.JUnitFramework") - def ivyDeps = Agg(ivy"com.novocode:junit-interface:0.11") - } - - object core extends JavaModule{ - object test extends Tests with JUnitTests - } - object app extends JavaModule{ - def moduleDeps = Seq(core) - object test extends Tests with JUnitTests - } - } - val resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-java" - - def init()(implicit tp: TestPath) = { - val eval = new TestEvaluator(HelloJava) - os.remove.all(HelloJava.millSourcePath) - os.remove.all(eval.outPath) - os.makeDir.all(HelloJava.millSourcePath / os.up) - os.copy(resourcePath, HelloJava.millSourcePath) - eval - } - def tests: Tests = Tests { - 'compile - { - val eval = init() - - val Right((res1, n1)) = eval.apply(HelloJava.core.compile) - val Right((res2, 0)) = eval.apply(HelloJava.core.compile) - val Right((res3, n2)) = eval.apply(HelloJava.app.compile) - - assert( - res1 == res2, - n1 != 0, - n2 != 0, - os.walk(res1.classes.path).exists(_.last == "Core.class"), - !os.walk(res1.classes.path).exists(_.last == "Main.class"), - os.walk(res3.classes.path).exists(_.last == "Main.class"), - !os.walk(res3.classes.path).exists(_.last == "Core.class") - ) - } - 'docJar - { - val eval = init() - - val Right((ref1, _)) = eval.apply(HelloJava.core.docJar) - val Right((ref2, _)) = eval.apply(HelloJava.app.docJar) - - assert( - os.proc("jar", "tf", ref1.path).call().out.lines.contains("hello/Core.html"), - os.proc("jar", "tf", ref2.path).call().out.lines.contains("hello/Main.html") - ) - } - 'test - { - val eval = init() - - val Left(Result.Failure(ref1, Some(v1))) = eval.apply(HelloJava.core.test.test()) - - assert( - v1._2(0).fullyQualifiedName == "hello.MyCoreTests.lengthTest", - v1._2(0).status == "Success", - v1._2(1).fullyQualifiedName == "hello.MyCoreTests.msgTest", - v1._2(1).status == "Failure" - ) - - val Right((v2, _)) = eval.apply(HelloJava.app.test.test()) - - assert( - v2._2(0).fullyQualifiedName == "hello.MyAppTests.appTest", - v2._2(0).status == "Success", - v2._2(1).fullyQualifiedName == "hello.MyAppTests.coreTest", - v2._2(1).status == "Success" - ) - } - 'failures - { - val eval = init() - - val mainJava = HelloJava.millSourcePath / 'app / 'src / 'hello / "Main.java" - val coreJava = HelloJava.millSourcePath / 'core / 'src / 'hello / "Core.java" - - val Right(_) = eval.apply(HelloJava.core.compile) - val Right(_) = eval.apply(HelloJava.app.compile) - - ammonite.ops.write.over(mainJava, ammonite.ops.read(mainJava) + "}") - - val Right(_) = eval.apply(HelloJava.core.compile) - val Left(_) = eval.apply(HelloJava.app.compile) - - ammonite.ops.write.over(coreJava, ammonite.ops.read(coreJava) + "}") - - val Left(_) = eval.apply(HelloJava.core.compile) - val Left(_) = eval.apply(HelloJava.app.compile) - - ammonite.ops.write.over(mainJava, ammonite.ops.read(mainJava).dropRight(1)) - ammonite.ops.write.over(coreJava, ammonite.ops.read(coreJava).dropRight(1)) - - val Right(_) = eval.apply(HelloJava.core.compile) - val Right(_) = eval.apply(HelloJava.app.compile) - } - } -} diff --git a/scalalib/test/src/mill/scalalib/HelloWorldTests.scala b/scalalib/test/src/mill/scalalib/HelloWorldTests.scala deleted file mode 100644 index da08f056..00000000 --- a/scalalib/test/src/mill/scalalib/HelloWorldTests.scala +++ /dev/null @@ -1,934 +0,0 @@ -package mill.scalalib - -import java.io.ByteArrayOutputStream -import java.util.jar.JarFile - -import mill._ -import mill.define.Target -import mill.api.Result.Exception -import mill.eval.{Evaluator, Result} -import mill.modules.Assembly -import mill.scalalib.publish._ -import mill.util.{TestEvaluator, TestUtil} -import mill.scalalib.publish.VersionControl -import utest._ -import utest.framework.TestPath - -import scala.collection.JavaConverters._ -import scala.util.Properties.isJavaAtLeast - - -object HelloWorldTests extends TestSuite { - trait HelloBase extends TestUtil.BaseModule{ - def millSourcePath = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') - } - - trait HelloWorldModule extends scalalib.ScalaModule { - def scalaVersion = "2.12.4" - } - - trait HelloWorldModuleWithMain extends HelloWorldModule { - def mainClass = Some("Main") - } - - object HelloWorld extends HelloBase { - object core extends HelloWorldModule - } - object CrossHelloWorld extends HelloBase { - object core extends Cross[HelloWorldCross]("2.10.6", "2.11.11", "2.12.3", "2.12.4", "2.13.0-M3") - class HelloWorldCross(val crossScalaVersion: String) extends CrossScalaModule - } - - object HelloWorldDefaultMain extends HelloBase { - object core extends HelloWorldModule - } - - object HelloWorldWithoutMain extends HelloBase { - object core extends HelloWorldModule{ - def mainClass = None - } - } - - object HelloWorldWithMain extends HelloBase { - object core extends HelloWorldModuleWithMain - } - - val akkaHttpDeps = Agg(ivy"com.typesafe.akka::akka-http:10.0.13") - - object HelloWorldAkkaHttpAppend extends HelloBase { - object core extends HelloWorldModuleWithMain { - def ivyDeps = akkaHttpDeps - - def assemblyRules = Seq(Assembly.Rule.Append("reference.conf")) - } - } - - object HelloWorldAkkaHttpExclude extends HelloBase { - object core extends HelloWorldModuleWithMain { - def ivyDeps = akkaHttpDeps - - def assemblyRules = Seq(Assembly.Rule.Exclude("reference.conf")) - } - } - - object HelloWorldAkkaHttpAppendPattern extends HelloBase { - object core extends HelloWorldModuleWithMain { - def ivyDeps = akkaHttpDeps - - def assemblyRules = Seq(Assembly.Rule.AppendPattern(".*.conf")) - } - } - - object HelloWorldAkkaHttpExcludePattern extends HelloBase { - object core extends HelloWorldModuleWithMain { - def ivyDeps = akkaHttpDeps - - def assemblyRules = Seq(Assembly.Rule.ExcludePattern(".*.conf")) - } - } - - object HelloWorldAkkaHttpNoRules extends HelloBase { - object core extends HelloWorldModuleWithMain { - def ivyDeps = akkaHttpDeps - - def assemblyRules = Seq.empty - } - } - - object HelloWorldMultiAppend extends HelloBase { - object core extends HelloWorldModuleWithMain { - def moduleDeps = Seq(model) - - def assemblyRules = Seq(Assembly.Rule.Append("reference.conf")) - } - object model extends HelloWorldModule - } - - object HelloWorldMultiExclude extends HelloBase { - object core extends HelloWorldModuleWithMain { - def moduleDeps = Seq(model) - - def assemblyRules = Seq(Assembly.Rule.Exclude("reference.conf")) - } - object model extends HelloWorldModule - } - - object HelloWorldMultiAppendPattern extends HelloBase { - object core extends HelloWorldModuleWithMain { - def moduleDeps = Seq(model) - - def assemblyRules = Seq(Assembly.Rule.AppendPattern(".*.conf")) - } - object model extends HelloWorldModule - } - - object HelloWorldMultiExcludePattern extends HelloBase { - object core extends HelloWorldModuleWithMain { - def moduleDeps = Seq(model) - - def assemblyRules = Seq(Assembly.Rule.ExcludePattern(".*.conf")) - } - object model extends HelloWorldModule - } - - object HelloWorldMultiNoRules extends HelloBase { - object core extends HelloWorldModuleWithMain { - def moduleDeps = Seq(model) - - def assemblyRules = Seq.empty - } - object model extends HelloWorldModule - } - - object HelloWorldWarnUnused extends HelloBase { - object core extends HelloWorldModule { - def scalacOptions = T(Seq("-Ywarn-unused")) - } - } - - object HelloWorldFatalWarnings extends HelloBase { - object core extends HelloWorldModule { - def scalacOptions = T(Seq("-Ywarn-unused", "-Xfatal-warnings")) - } - } - - object HelloWorldWithDocVersion extends HelloBase { - object core extends HelloWorldModule { - def scalacOptions = T(Seq("-Ywarn-unused", "-Xfatal-warnings")) - def scalaDocOptions = super.scalaDocOptions() ++ Seq("-doc-version", "1.2.3") - } - } - - object HelloWorldOnlyDocVersion extends HelloBase { - object core extends HelloWorldModule { - def scalacOptions = T(Seq("-Ywarn-unused", "-Xfatal-warnings")) - def scalaDocOptions = T(Seq("-doc-version", "1.2.3")) - } - } - - object HelloWorldDocTitle extends HelloBase { - object core extends HelloWorldModule { - def scalaDocOptions = T(Seq("-doc-title", "Hello World")) - } - } - - object HelloWorldWithPublish extends HelloBase{ - object core extends 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.Common.Apache2), - versionControl = VersionControl.github("lihaoyi", "hello-world-publish"), - developers = - Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi")) - ) - } - } - - object HelloWorldScalaOverride extends HelloBase{ - object core extends HelloWorldModule { - - override def scalaVersion: Target[String] = "2.11.11" - } - } - - object HelloWorldIvyDeps extends HelloBase{ - object moduleA extends HelloWorldModule { - - override def ivyDeps = Agg(ivy"com.lihaoyi::sourcecode:0.1.3") - } - object moduleB extends HelloWorldModule { - override def moduleDeps = Seq(moduleA) - override def ivyDeps = Agg(ivy"com.lihaoyi::sourcecode:0.1.4") - } - } - - object HelloWorldTypeLevel extends HelloBase{ - object foo extends ScalaModule { - def scalaVersion = "2.11.8" - override def scalaOrganization = "org.typelevel" - - def ivyDeps = Agg( - ivy"com.github.julien-truffaut::monocle-macro::1.4.0" - ) - def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ Agg( - ivy"org.scalamacros:::paradise:2.1.0" - ) - def scalaDocPluginIvyDeps = super.scalaDocPluginIvyDeps() ++ Agg( - ivy"com.typesafe.genjavadoc:::genjavadoc-plugin:0.11" - ) - } - } - - object HelloWorldMacros extends HelloBase{ - object core extends ScalaModule { - def scalaVersion = "2.12.4" - - def ivyDeps = Agg( - ivy"com.github.julien-truffaut::monocle-macro::1.4.0" - ) - def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ Agg( - ivy"org.scalamacros:::paradise:2.1.0" - ) - } - } - - object HelloWorldFlags extends HelloBase{ - object core extends ScalaModule { - def scalaVersion = "2.12.4" - - def scalacOptions = super.scalacOptions() ++ Seq( - "-Ypartial-unification" - ) - } - } - - object HelloScalacheck extends HelloBase{ - object foo extends ScalaModule { - def scalaVersion = "2.12.4" - object test extends Tests { - def ivyDeps = Agg(ivy"org.scalacheck::scalacheck:1.13.5") - def testFrameworks = Seq("org.scalacheck.ScalaCheckFramework") - } - } - } - - object HelloDotty extends HelloBase{ - object foo extends ScalaModule { - def scalaVersion = "0.9.0-RC1" - def ivyDeps = Agg(ivy"org.typelevel::cats-core:1.2.0".withDottyCompat(scalaVersion())) - } - } - - val resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world" - - 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 jarEntries(jar: JarFile): Set[String] = { - jar.entries().asScala.map(_.getName).toSet - } - - def readFileFromJar(jar: JarFile, name: String): String = { - val is = jar.getInputStream(jar.getEntry(name)) - val baos = new ByteArrayOutputStream() - os.Internals.transfer(is, baos) - new String(baos.toByteArray) - } - - def compileClassfiles = Seq[os.RelPath]( - "Main.class", - "Main$.class", - "Main0.class", - "Main0$.class", - "Main$delayedInit$body.class", - "Person.class", - "Person$.class" - ) - - def workspaceTest[T](m: TestUtil.BaseModule, resourcePath: os.Path = resourcePath) - (t: TestEvaluator => T) - (implicit tp: TestPath): T = { - val eval = new TestEvaluator(m) - os.remove.all(m.millSourcePath) - os.remove.all(eval.outPath) - os.makeDir.all(m.millSourcePath / os.up) - os.copy(resourcePath, m.millSourcePath) - t(eval) - } - - - - - def tests: Tests = Tests { - 'scalaVersion - { - - 'fromBuild - workspaceTest(HelloWorld){eval => - val Right((result, evalCount)) = eval.apply(HelloWorld.core.scalaVersion) - - assert( - result == "2.12.4", - evalCount > 0 - ) - } - 'override - workspaceTest(HelloWorldScalaOverride){eval => - val Right((result, evalCount)) = eval.apply(HelloWorldScalaOverride.core.scalaVersion) - - assert( - result == "2.11.11", - evalCount > 0 - ) - } - } - - 'scalacOptions - { - 'emptyByDefault - workspaceTest(HelloWorld){eval => - val Right((result, evalCount)) = eval.apply(HelloWorld.core.scalacOptions) - - assert( - result.isEmpty, - evalCount > 0 - ) - } - 'override - workspaceTest(HelloWorldFatalWarnings){ eval => - val Right((result, evalCount)) = eval.apply(HelloWorldFatalWarnings.core.scalacOptions) - - assert( - result == Seq("-Ywarn-unused", "-Xfatal-warnings"), - evalCount > 0 - ) - } - } - - 'scalaDocOptions - { - 'emptyByDefault - workspaceTest(HelloWorld){eval => - val Right((result, evalCount)) = eval.apply(HelloWorld.core.scalaDocOptions) - assert( - result.isEmpty, - evalCount > 0 - ) - } - 'override - workspaceTest(HelloWorldDocTitle){ eval => - val Right((result, evalCount)) = eval.apply(HelloWorldDocTitle.core.scalaDocOptions) - assert( - result == Seq("-doc-title", "Hello World"), - evalCount > 0 - ) - } - 'extend - workspaceTest(HelloWorldWithDocVersion){ eval => - val Right((result, evalCount)) = eval.apply(HelloWorldWithDocVersion.core.scalaDocOptions) - assert( - result == Seq("-Ywarn-unused", "-Xfatal-warnings", "-doc-version", "1.2.3"), - evalCount > 0 - ) - } - // make sure options are passed during ScalaDoc generation - 'docJarWithTitle - workspaceTest( - HelloWorldDocTitle, - resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world" - ){ eval => - val Right((_, evalCount)) = eval.apply(HelloWorldDocTitle.core.docJar) - assert( - evalCount > 0, - os.read(eval.outPath / 'core / 'docJar / 'dest / 'javadoc / "index.html").contains("Hello World") - ) - } - 'docJarWithVersion - workspaceTest( - HelloWorldWithDocVersion, - resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world" - ){ eval => - // scaladoc generation fails because of "-Xfatal-warnings" flag - val Left(Result.Failure("docJar generation failed", None)) = eval.apply(HelloWorldWithDocVersion.core.docJar) - } - 'docJarOnlyVersion - workspaceTest( - HelloWorldOnlyDocVersion, - resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world" - ){ eval => - val Right((_, evalCount)) = eval.apply(HelloWorldOnlyDocVersion.core.docJar) - assert( - evalCount > 0, - os.read(eval.outPath / 'core / 'docJar / 'dest / 'javadoc / "index.html").contains("1.2.3") - ) - } - } - - 'scalacPluginClasspath - { - 'withMacroParadise - workspaceTest(HelloWorldTypeLevel){eval => - val Right((result, evalCount)) = eval.apply(HelloWorldTypeLevel.foo.scalacPluginClasspath) - assert( - result.nonEmpty, - result.exists { pathRef => pathRef.path.segments.contains("scalamacros") }, - evalCount > 0 - ) - } - } - - 'scalaDocPluginClasspath - { - 'extend - workspaceTest(HelloWorldTypeLevel){eval => - val Right((result, evalCount)) = eval.apply(HelloWorldTypeLevel.foo.scalaDocPluginClasspath) - assert( - result.nonEmpty, - result.exists { pathRef => pathRef.path.segments.contains("scalamacros") }, - result.exists { pathRef => pathRef.path.segments.contains("genjavadoc") }, - evalCount > 0 - ) - } - } - - 'compile - { - 'fromScratch - workspaceTest(HelloWorld){eval => - val Right((result, evalCount)) = eval.apply(HelloWorld.core.compile) - - val analysisFile = result.analysisFile - val outputFiles = os.walk(result.classes.path) - val expectedClassfiles = compileClassfiles.map( - eval.outPath / 'core / 'compile / 'dest / 'classes / _ - ) - assert( - result.classes.path == eval.outPath / 'core / 'compile / 'dest / 'classes, - os.exists(analysisFile), - outputFiles.nonEmpty, - outputFiles.forall(expectedClassfiles.contains), - evalCount > 0 - ) - - // don't recompile if nothing changed - val Right((_, unchangedEvalCount)) = eval.apply(HelloWorld.core.compile) - - assert(unchangedEvalCount == 0) - } - 'recompileOnChange - workspaceTest(HelloWorld){eval => - val Right((_, freshCount)) = eval.apply(HelloWorld.core.compile) - assert(freshCount > 0) - - os.write.append(HelloWorld.millSourcePath / 'core / 'src / "Main.scala", "\n") - - val Right((_, incCompileCount)) = eval.apply(HelloWorld.core.compile) - assert(incCompileCount > 0, incCompileCount < freshCount) - } - 'failOnError - workspaceTest(HelloWorld){eval => - os.write.append(HelloWorld.millSourcePath / 'core / 'src / "Main.scala", "val x: ") - - val Left(Result.Failure("Compilation failed", _)) = eval.apply(HelloWorld.core.compile) - - - val paths = Evaluator.resolveDestPaths( - eval.outPath, - HelloWorld.core.compile.ctx.segments - ) - - assert( - os.walk(paths.dest / 'classes).isEmpty, - !os.exists(paths.meta) - ) - // Works when fixed - os.write.over( - HelloWorld.millSourcePath / 'core / 'src / "Main.scala", - os.read(HelloWorld.millSourcePath / 'core / 'src / "Main.scala").dropRight("val x: ".length) - ) - - val Right((result, evalCount)) = eval.apply(HelloWorld.core.compile) - } - 'passScalacOptions - workspaceTest(HelloWorldFatalWarnings){ eval => - // compilation fails because of "-Xfatal-warnings" flag - val Left(Result.Failure("Compilation failed", _)) = eval.apply(HelloWorldFatalWarnings.core.compile) - } - } - - 'runMain - { - 'runMainObject - workspaceTest(HelloWorld){eval => - val runResult = eval.outPath / 'core / 'runMain / 'dest / "hello-mill" - - val Right((_, evalCount)) = eval.apply(HelloWorld.core.runMain("Main", runResult.toString)) - assert(evalCount > 0) - - assert( - os.exists(runResult), - os.read(runResult) == "hello rockjam, your age is: 25" - ) - } - 'runCross - { - def cross(eval: TestEvaluator, v: String, expectedOut: String) { - - val runResult = eval.outPath / "hello-mill" - - val Right((_, evalCount)) = eval.apply( - CrossHelloWorld.core(v).runMain("Shim", runResult.toString) - ) - - assert(evalCount > 0) - - - assert( - os.exists(runResult), - os.read(runResult) == expectedOut - ) - } - 'v210 - TestUtil.disableInJava9OrAbove(workspaceTest(CrossHelloWorld)(cross(_, "2.10.6", "2.10.6 rox"))) - 'v211 - TestUtil.disableInJava9OrAbove(workspaceTest(CrossHelloWorld)(cross(_, "2.11.11", "2.11.11 pwns"))) - 'v2123 - workspaceTest(CrossHelloWorld)(cross(_, "2.12.3", "2.12.3 leet")) - 'v2124 - workspaceTest(CrossHelloWorld)(cross(_, "2.12.4", "2.12.4 leet")) - 'v2130M3 - workspaceTest(CrossHelloWorld)(cross(_, "2.13.0-M3", "2.13.0-M3 idk")) - } - - - 'notRunInvalidMainObject - workspaceTest(HelloWorld){eval => - val Left(Result.Failure("subprocess failed", _)) = eval.apply(HelloWorld.core.runMain("Invalid")) - } - 'notRunWhenCompileFailed - workspaceTest(HelloWorld){eval => - os.write.append(HelloWorld.millSourcePath / 'core / 'src / "Main.scala", "val x: ") - - val Left(Result.Failure("Compilation failed", _)) = eval.apply(HelloWorld.core.runMain("Main")) - - } - } - - 'forkRun - { - 'runIfMainClassProvided - workspaceTest(HelloWorldWithMain){eval => - val runResult = eval.outPath / 'core / 'run / 'dest / "hello-mill" - val Right((_, evalCount)) = eval.apply( - HelloWorldWithMain.core.run(runResult.toString) - ) - - assert(evalCount > 0) - - - assert( - os.exists(runResult), - os.read(runResult) == "hello rockjam, your age is: 25" - ) - } - 'notRunWithoutMainClass - workspaceTest( - HelloWorldWithoutMain, - os.pwd / 'scalalib / 'test / 'resources / "hello-world-no-main" - ){eval => - val Left(Result.Failure(_, None)) = eval.apply(HelloWorldWithoutMain.core.run()) - } - - 'runDiscoverMainClass - workspaceTest(HelloWorldWithoutMain){eval => - // Make sure even if there isn't a main class defined explicitly, it gets - // discovered by Zinc and used - val runResult = eval.outPath / 'core / 'run / 'dest / "hello-mill" - val Right((_, evalCount)) = eval.apply( - HelloWorldWithoutMain.core.run(runResult.toString) - ) - - assert(evalCount > 0) - - - assert( - os.exists(runResult), - os.read(runResult) == "hello rockjam, your age is: 25" - ) - } - } - - 'run - { - 'runIfMainClassProvided - workspaceTest(HelloWorldWithMain){eval => - val runResult = eval.outPath / 'core / 'run / 'dest / "hello-mill" - val Right((_, evalCount)) = eval.apply( - HelloWorldWithMain.core.runLocal(runResult.toString) - ) - - assert(evalCount > 0) - - - assert( - os.exists(runResult), - os.read(runResult) == "hello rockjam, your age is: 25" - ) - } - 'runWithDefaultMain - workspaceTest(HelloWorldDefaultMain){eval => - val runResult = eval.outPath / 'core / 'run / 'dest / "hello-mill" - val Right((_, evalCount)) = eval.apply( - HelloWorldDefaultMain.core.runLocal(runResult.toString) - ) - - assert(evalCount > 0) - - - assert( - os.exists(runResult), - os.read(runResult) == "hello rockjam, your age is: 25" - ) - } - 'notRunWithoutMainClass - workspaceTest( - HelloWorldWithoutMain, - os.pwd / 'scalalib / 'test / 'resources / "hello-world-no-main" - ){eval => - val Left(Result.Failure(_, None)) = eval.apply(HelloWorldWithoutMain.core.runLocal()) - - } - } - - 'jar - { - 'nonEmpty - workspaceTest(HelloWorldWithMain){eval => - val Right((result, evalCount)) = eval.apply(HelloWorldWithMain.core.jar) - - assert( - os.exists(result.path), - evalCount > 0 - ) - - val jarFile = new JarFile(result.path.toIO) - val entries = jarFile.entries().asScala.map(_.getName).toSet - - val otherFiles = Seq[os.RelPath]( - os.rel / "META-INF" / "MANIFEST.MF", - "reference.conf" - ) - val expectedFiles = compileClassfiles ++ otherFiles - - assert( - entries.nonEmpty, - entries == expectedFiles.map(_.toString()).toSet - ) - - val mainClass = jarMainClass(jarFile) - assert(mainClass.contains("Main")) - } - - 'logOutputToFile - workspaceTest(HelloWorld){eval => - val outPath = eval.outPath - eval.apply(HelloWorld.core.compile) - - val logFile = outPath / 'core / 'compile / 'log - assert(os.exists(logFile)) - } - } - - 'assembly - { - 'assembly - workspaceTest(HelloWorldWithMain){ eval => - val Right((result, evalCount)) = eval.apply(HelloWorldWithMain.core.assembly) - assert( - os.exists(result.path), - evalCount > 0 - ) - val jarFile = new JarFile(result.path.toIO) - val entries = jarEntries(jarFile) - - val mainPresent = entries.contains("Main.class") - assert(mainPresent) - assert(entries.exists(s => s.contains("scala/Predef.class"))) - - val mainClass = jarMainClass(jarFile) - assert(mainClass.contains("Main")) - } - - 'assemblyRules - { - def checkAppend[M <: TestUtil.BaseModule](module: M, - target: Target[PathRef]) = - workspaceTest(module) { eval => - val Right((result, _)) = eval.apply(target) - - val jarFile = new JarFile(result.path.toIO) - - assert(jarEntries(jarFile).contains("reference.conf")) - - val referenceContent = readFileFromJar(jarFile, "reference.conf") - - assert( - // akka modules configs are present - referenceContent.contains("akka-http Reference Config File"), - referenceContent.contains("akka-http-core Reference Config File"), - referenceContent.contains("Akka Actor Reference Config File"), - referenceContent.contains("Akka Stream Reference Config File"), - // our application config is present too - referenceContent.contains("My application Reference Config File"), - referenceContent.contains( - """akka.http.client.user-agent-header="hello-world-client"""" - ) - ) - } - - val helloWorldMultiResourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-multi" - - def checkAppendMulti[M <: TestUtil.BaseModule]( - module: M, - target: Target[PathRef]) = - workspaceTest( - module, - resourcePath = helloWorldMultiResourcePath - ) { eval => - val Right((result, _)) = eval.apply(target) - - val jarFile = new JarFile(result.path.toIO) - - assert(jarEntries(jarFile).contains("reference.conf")) - - val referenceContent = readFileFromJar(jarFile, "reference.conf") - - assert( - // reference config from core module - referenceContent.contains("Core Reference Config File"), - // reference config from model module - referenceContent.contains("Model Reference Config File"), - // concatenated content - referenceContent.contains("bar.baz=hello"), - referenceContent.contains("foo.bar=2") - ) - } - - 'appendWithDeps - checkAppend( - HelloWorldAkkaHttpAppend, - HelloWorldAkkaHttpAppend.core.assembly - ) - 'appendMultiModule - checkAppendMulti( - HelloWorldMultiAppend, - HelloWorldMultiAppend.core.assembly - ) - 'appendPatternWithDeps - checkAppend( - HelloWorldAkkaHttpAppendPattern, - HelloWorldAkkaHttpAppendPattern.core.assembly - ) - 'appendPatternMultiModule - checkAppendMulti( - HelloWorldMultiAppendPattern, - HelloWorldMultiAppendPattern.core.assembly - ) - - def checkExclude[M <: TestUtil.BaseModule](module: M, - target: Target[PathRef], - resourcePath: os.Path = resourcePath - ) = - workspaceTest(module, resourcePath) { eval => - val Right((result, _)) = eval.apply(target) - - val jarFile = new JarFile(result.path.toIO) - - assert(!jarEntries(jarFile).contains("reference.conf")) - } - - 'excludeWithDeps - checkExclude( - HelloWorldAkkaHttpExclude, - HelloWorldAkkaHttpExclude.core.assembly - ) - 'excludeMultiModule - checkExclude( - HelloWorldMultiExclude, - HelloWorldMultiExclude.core.assembly, - resourcePath = helloWorldMultiResourcePath - - ) - 'excludePatternWithDeps - checkExclude( - HelloWorldAkkaHttpExcludePattern, - HelloWorldAkkaHttpExcludePattern.core.assembly - ) - 'excludePatternMultiModule - checkExclude( - HelloWorldMultiExcludePattern, - HelloWorldMultiExcludePattern.core.assembly, - resourcePath = helloWorldMultiResourcePath - ) - - 'writeFirstWhenNoRule - { - 'withDeps - workspaceTest(HelloWorldAkkaHttpNoRules) { eval => - val Right((result, _)) = eval.apply(HelloWorldAkkaHttpNoRules.core.assembly) - - val jarFile = new JarFile(result.path.toIO) - - assert(jarEntries(jarFile).contains("reference.conf")) - - val referenceContent = readFileFromJar(jarFile, "reference.conf") - - val allOccurrences = Seq( - referenceContent.contains("akka-http Reference Config File"), - referenceContent.contains("akka-http-core Reference Config File"), - referenceContent.contains("Akka Actor Reference Config File"), - referenceContent.contains("Akka Stream Reference Config File"), - referenceContent.contains("My application Reference Config File") - ) - - val timesOcccurres = allOccurrences.find(identity).size - - assert(timesOcccurres == 1) - } - - 'multiModule - workspaceTest( - HelloWorldMultiNoRules, - resourcePath = helloWorldMultiResourcePath - ) { eval => - val Right((result, _)) = eval.apply(HelloWorldMultiNoRules.core.assembly) - - val jarFile = new JarFile(result.path.toIO) - - assert(jarEntries(jarFile).contains("reference.conf")) - - val referenceContent = readFileFromJar(jarFile, "reference.conf") - - assert( - referenceContent.contains("Model Reference Config File"), - referenceContent.contains("foo.bar=2"), - - !referenceContent.contains("Core Reference Config File"), - !referenceContent.contains("bar.baz=hello") - ) - } - } - } - - 'run - workspaceTest(HelloWorldWithMain){eval => - val Right((result, evalCount)) = eval.apply(HelloWorldWithMain.core.assembly) - - assert( - os.exists(result.path), - evalCount > 0 - ) - val runResult = eval.outPath / "hello-mill" - - os.proc("java", "-jar", result.path, runResult).call(cwd = eval.outPath) - - assert( - os.exists(runResult), - os.read(runResult) == "hello rockjam, your age is: 25" - ) - } - } - - 'ivyDeps - workspaceTest(HelloWorldIvyDeps){ eval => - val Right((result, _)) = eval.apply(HelloWorldIvyDeps.moduleA.runClasspath) - assert( - result.exists(_.path.last == "sourcecode_2.12-0.1.3.jar"), - !result.exists(_.path.last == "sourcecode_2.12-0.1.4.jar") - ) - - val Right((result2, _)) = eval.apply(HelloWorldIvyDeps.moduleB.runClasspath) - assert( - result2.exists(_.path.last == "sourcecode_2.12-0.1.4.jar"), - !result2.exists(_.path.last == "sourcecode_2.12-0.1.3.jar") - ) - } - - 'typeLevel - workspaceTest(HelloWorldTypeLevel){ eval => - val classPathsToCheck = Seq( - HelloWorldTypeLevel.foo.runClasspath, - HelloWorldTypeLevel.foo.ammoniteReplClasspath, - HelloWorldTypeLevel.foo.compileClasspath - ) - for(cp <- classPathsToCheck){ - val Right((result, _)) = eval.apply(cp) - assert( - // Make sure every relevant piece org.scala-lang has been substituted for org.typelevel - !result.map(_.toString).exists(x => - x.contains("scala-lang") && - (x.contains("scala-library") || x.contains("scala-compiler") || x.contains("scala-reflect")) - ), - result.map(_.toString).exists(x => x.contains("typelevel") && x.contains("scala-library")) - - ) - } - } - - 'macros - { - // make sure macros are applied when compiling/running - 'runMain - workspaceTest( - HelloWorldMacros, - resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-macros" - ){ eval => - val Right((_, evalCount)) = eval.apply(HelloWorldMacros.core.runMain("Main")) - assert(evalCount > 0) - } - // make sure macros are applied when compiling during scaladoc generation - 'docJar - workspaceTest( - HelloWorldMacros, - resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-macros" - ){ eval => - val Right((_, evalCount)) = eval.apply(HelloWorldMacros.core.docJar) - assert(evalCount > 0) - } - } - - 'flags - { - // make sure flags are passed when compiling/running - 'runMain - workspaceTest( - HelloWorldFlags, - resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-flags" - ){ eval => - val Right((_, evalCount)) = eval.apply(HelloWorldFlags.core.runMain("Main")) - assert(evalCount > 0) - } - // make sure flags are passed during ScalaDoc generation - 'docJar - workspaceTest( - HelloWorldFlags, - resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-world-flags" - ){ eval => - val Right((_, evalCount)) = eval.apply(HelloWorldFlags.core.docJar) - assert(evalCount > 0) - } - } - - 'scalacheck - workspaceTest( - HelloScalacheck, - resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-scalacheck" - ){ eval => - val Right((res, evalCount)) = eval.apply(HelloScalacheck.foo.test.test()) - assert( - evalCount > 0, - res._2.map(_.selector) == Seq( - "String.startsWith", - "String.endsWith", - "String.substring", - "String.substring" - ) - ) - } - - 'dotty - workspaceTest( - HelloDotty, - resourcePath = os.pwd / 'scalalib / 'test / 'resources / "hello-dotty" - ){ eval => - if (isJavaAtLeast("9")) { - // Skip the test because Dotty does not support Java >= 9 yet - // (see https://github.com/lampepfl/dotty/pull/3138) - } else { - val Right((_, evalCount)) = eval.apply(HelloDotty.foo.run()) - assert(evalCount > 0) - } - } - } -} diff --git a/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala b/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala deleted file mode 100644 index 78361625..00000000 --- a/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala +++ /dev/null @@ -1,77 +0,0 @@ -package mill.scalalib - -import coursier.Cache -import coursier.maven.MavenRepository -import mill.api.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, - Lib.depToDependency(_, "2.12.4", ""), - deps - ) - - val tests = Tests { - 'resolveValidDeps - { - val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3") - val Success(paths) = evalDeps(deps) - assert(paths.nonEmpty) - } - - 'resolveValidDepsWithClassifier - { - val deps = Agg(ivy"org.lwjgl:lwjgl:3.1.1;classifier=natives-macos") - val Success(paths) = evalDeps(deps) - assert(paths.nonEmpty) - assert(paths.items.next.path.toString.contains("natives-macos")) - } - - 'resolveTransitiveRuntimeDeps - { - val deps = Agg(ivy"org.mockito:mockito-core:2.7.22") - val Success(paths) = evalDeps(deps) - assert(paths.nonEmpty) - assert(paths.exists(_.path.toString.contains("objenesis"))) - assert(paths.exists(_.path.toString.contains("byte-buddy"))) - } - - 'excludeTransitiveDeps - { - val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".exclude("com.lihaoyi" -> "fansi_2.12")) - val Success(paths) = evalDeps(deps) - assert(!paths.exists(_.path.toString.contains("fansi_2.12"))) - } - - 'excludeTransitiveDepsByOrg - { - val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".excludeOrg("com.lihaoyi")) - val Success(paths) = evalDeps(deps) - assert(!paths.exists(path => path.path.toString.contains("com/lihaoyi") && !path.path.toString.contains("pprint_2.12"))) - } - - 'excludeTransitiveDepsByName - { - val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3".excludeName("fansi_2.12")) - val Success(paths) = evalDeps(deps) - assert(!paths.exists(_.path.toString.contains("fansi_2.12"))) - } - - '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/src/mill/scalalib/VersionControlTests.scala b/scalalib/test/src/mill/scalalib/VersionControlTests.scala deleted file mode 100644 index fafdca2d..00000000 --- a/scalalib/test/src/mill/scalalib/VersionControlTests.scala +++ /dev/null @@ -1,74 +0,0 @@ -package mill.scalalib - -import mill.scalalib.publish.{VersionControl, VersionControlConnection} - -import utest._ - -object VersionContolTests extends TestSuite { - - import VersionControl._ - import VersionControlConnection._ - - val tests = Tests { - 'github - { - assert( - github("lihaoyi", "mill") == - VersionControl( - browsableRepository = Some("https://github.com/lihaoyi/mill"), - connection = Some("scm:git:git://github.com/lihaoyi/mill.git"), - developerConnection = Some("scm:git:ssh://git@github.com:lihaoyi/mill.git"), - tag = None - ) - ) - } - 'git - { - assert( - gitGit("example.org", "path.git", port = Some(9418)) == - "scm:git:git://example.org:9418/path.git" - ) - - assert( - gitHttp("example.org") == - "scm:git:http://example.org/" - ) - - assert( - gitHttps("example.org", "path.git") == - "scm:git:https://example.org/path.git" - ) - - assert( - gitSsh("example.org", "path.git") == - "scm:git:ssh://example.org/path.git" - ) - - assert( - gitFile("/home/gui/repos/foo/bare.git") == - "scm:git:file:///home/gui/repos/foo/bare.git" - ) - - } - 'svn - { - assert( - svnSsh("example.org", "repo") == - "scm:svn:svn+ssh://example.org/repo" - ) - assert( - svnHttp("example.org", "repo", Some("user"), Some("pass")) == - "scm:svn:http://user:pass@example.org/repo" - ) - assert( - svnHttps("example.org", "repo", Some("user")) == - "scm:svn:https://user@example.org/repo" - ) - assert( - svnSvn("example.org", "repo", port = Some(3690)) == - "scm:svn:svn://example.org:3690/repo" - ) - assert( - svnFile("/var/svn/repo") == - "scm:svn:file:///var/svn/repo" - ) - } - } -} \ No newline at end of file diff --git a/scalalib/test/src/mill/scalalib/dependency/metadata/MetadataLoaderFactoryTests.scala b/scalalib/test/src/mill/scalalib/dependency/metadata/MetadataLoaderFactoryTests.scala deleted file mode 100644 index 4c2206b8..00000000 --- a/scalalib/test/src/mill/scalalib/dependency/metadata/MetadataLoaderFactoryTests.scala +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file contains code originally published under the following license: - * - * Copyright (c) 2012, Roman Timushev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package mill.scalalib.dependency.metadata - -import coursier.Fetch.Content -import coursier.core.{Artifact, Module, Project, Repository} -import coursier.ivy.IvyRepository -import coursier.maven.MavenRepository -import coursier.util.{EitherT, Monad} -import utest._ - -object MetadataLoaderFactoryTests extends TestSuite { - - val tests = Tests { - 'mavenRepository - { - val mavenRepo = MavenRepository("https://repo1.maven.org/maven2") - assertMatch(MetadataLoaderFactory(mavenRepo)) { - case Some(MavenMetadataLoader(`mavenRepo`)) => - } - } - 'ivyRepository - { - val Right(ivyRepo) = IvyRepository.parse( - "https://dl.bintray.com/sbt/sbt-plugin-releases/" + coursier.ivy.Pattern.default.string, - dropInfoAttributes = true) - assertMatch(MetadataLoaderFactory(ivyRepo)) { case None => } - } - 'otherRepository - { - val otherRepo = new CustomRepository - assertMatch(MetadataLoaderFactory(otherRepo)) { case None => } - } - } - - case class CustomRepository() extends Repository { - override def find[F[_]](module: Module, version: String, fetch: Content[F])( - implicit F: Monad[F]): EitherT[F, String, (Artifact.Source, Project)] = - ??? - } -} diff --git a/scalalib/test/src/mill/scalalib/dependency/updates/UpdatesFinderTests.scala b/scalalib/test/src/mill/scalalib/dependency/updates/UpdatesFinderTests.scala deleted file mode 100644 index 7b6e6e36..00000000 --- a/scalalib/test/src/mill/scalalib/dependency/updates/UpdatesFinderTests.scala +++ /dev/null @@ -1,173 +0,0 @@ -/* - * This file contains code originally published under the following license: - * - * Copyright (c) 2012, Roman Timushev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package mill.scalalib.dependency.updates - -import mill.scalalib.dependency.versions.{DependencyVersions, Version} -import utest._ - -object UpdatesFinderTests extends TestSuite { - - private def updates(current: String, - available: Seq[String], - allowPreRelease: Boolean) = { - val dependency = coursier.Dependency( - coursier.Module("com.example.organization", "example-artifact"), - current) - val currentVersion = Version(current) - val allVersions = available.map(Version(_)).toSet - - UpdatesFinder - .findUpdates(DependencyVersions(dependency, currentVersion, allVersions), - allowPreRelease) - .updates - .map(_.toString) - } - - val available = Seq( - "0.9.9-SNAPSHOT", - "0.9.9-M3", - "0.9.9", - "1.0.0-SNAPSHOT", - "1.0.0-M2", - "1.0.0-M3", - "1.0.0", - "1.0.1-SNAPSHOT", - "1.0.1-M3", - "1.0.1" - ) - - val tests = Tests { - - 'snapshotArtifacts - { - val u = updates("1.0.0-SNAPSHOT", available, allowPreRelease = false) - val pu = updates("1.0.0-SNAPSHOT", available, allowPreRelease = true) - - 'noOldStableVersions - { - assert(!u.contains("0.9.9")) - } - 'noOldMilestones - { - assert(!u.contains("0.9.9-M3")) - } - 'noOldSnapshots - { - assert(!u.contains("0.9.9-SNAPSHOT")) - } - 'noCurrentMilestones - { - assert(!u.contains("1.0.0-M3")) - } - 'noCurrentSnapshot - { - assert(!u.contains("1.0.0-SNAPSHOT")) - } - 'stableUpdates - { - assert(u.contains("1.0.0") && u.contains("1.0.1")) - } - 'milestoneUpdates - { - assert(u.contains("1.0.1-M3")) - } - 'snapshotUpdates - { - assert(u.contains("1.0.1-SNAPSHOT")) - } - 'noDifferencesRegardingOptionalPreReleases - { - assert(u == pu) - } - } - - 'milestoneArtifacts - { - val u = updates("1.0.0-M2", available, allowPreRelease = false) - val pu = updates("1.0.0-M2", available, allowPreRelease = true) - - 'noOldStableVersions - { - assert(!u.contains("0.9.9")) - } - 'noOldSnapshots - { - assert(!u.contains("0.9.9-SNAPSHOT")) - } - 'noOldMilestones - { - assert(!u.contains("0.9.9-M3")) - } - 'noCurrentSnapshot - { - assert(!u.contains("1.0.0-SNAPSHOT")) - } - 'currentMilestones - { - assert(u.contains("1.0.0-M3")) - } - 'stableUpdates - { - assert(u.contains("1.0.1")) - } - 'noSnapshotUpdates - { - assert(!u.contains("1.0.1-SNAPSHOT")) - } - 'milestoneUpdates - { - assert(u.contains("1.0.1-M3")) - } - 'noDifferencesRegardingOptionalPreReleases - { - assert(u == pu) - } - } - - 'stableArtifacts - { - val u = updates("1.0.0", available, allowPreRelease = false) - val pu = updates("1.0.0", available, allowPreRelease = true) - - 'noOldStableVersions - { - assert(!u.contains("0.9.9")) - assert(!pu.contains("0.9.9")) - } - 'noOldSnapshots - { - assert(!u.contains("0.9.9-SNAPSHOT")) - assert(!pu.contains("0.9.9-SNAPSHOT")) - } - 'noOldMilestones - { - assert(!u.contains("0.9.9-M3")) - assert(!pu.contains("0.9.9-M3")) - } - 'noCurrentSnapshot - { - assert(!u.contains("1.0.0-SNAPSHOT")) - assert(!pu.contains("1.0.0-SNAPSHOT")) - } - 'noCurrentMilestones - { - assert(!u.contains("1.0.0-M3")) - assert(!pu.contains("1.0.0-M3")) - } - 'stableUpdates - { - assert(u.contains("1.0.1")) - assert(pu.contains("1.0.1")) - } - 'noSnapshotUpdates - { - assert(!u.contains("1.0.1-SNAPSHOT")) - assert(!pu.contains("1.0.1-SNAPSHOT")) - } - 'noMilestoneUpdates - { - assert(!u.contains("1.0.1-M3")) - } - 'milestoneUpdatesWhenAllowingPreReleases - { - assert(pu.contains("1.0.1-M3")) - } - } - } -} diff --git a/scalalib/test/src/mill/scalalib/dependency/versions/VersionTests.scala b/scalalib/test/src/mill/scalalib/dependency/versions/VersionTests.scala deleted file mode 100644 index b916c86f..00000000 --- a/scalalib/test/src/mill/scalalib/dependency/versions/VersionTests.scala +++ /dev/null @@ -1,138 +0,0 @@ -/* - * This file contains code originally published under the following license: - * - * Copyright (c) 2012, Roman Timushev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package mill.scalalib.dependency.versions - -import utest._ -import fastparse.Parsed - -object VersionTests extends TestSuite { - - val tests = Tests { - 'versionsClassification - { - 'ReleaseVersion - { - List("1.0.0", "1.0.0.Final", "1.0.0-FINAL", "1.0.0.RELEASE") foreach { - rel => - assertMatch(Version(rel)) { - case ReleaseVersion(List(1, 0, 0)) => - } - } - } - 'PreReleaseVersion - { - assertMatch(Version("1.0.0-alpha.1")) { - case PreReleaseVersion(List(1, 0, 0), List("alpha", "1")) => - } - } - 'PreReleaseBuildVersion - { - assertMatch(Version("1.0.0-alpha.1+build.10")) { - case PreReleaseBuildVersion(List(1, 0, 0), - List("alpha", "1"), - List("build", "10")) => - } - } - 'BuildVersion - { - assertMatch(Version("1.0.0+build.10")) { - case BuildVersion(List(1, 0, 0), List("build", "10")) => - } - } - } - - 'semverVersionsOrdering - { - import scala.Ordered._ - - val v = List( - "invalid", - "1.0.0-20131213005945", - "1.0.0-alpha", - "1.0.0-alpha.1", - "1.0.0-beta.2", - "1.0.0-beta.11", - "1.0.0-rc.1", - "1.0.0-rc.1+build.1", - "1.0.0", - "1.0.0+0.3.7", - "1.33.7+build", - "1.33.7+build.2.b8f12d7", - "1.33.7+build.11.e0f985a", - "2.0.M5b", - "2.0.M6-SNAP9", - "2.0.M6-SNAP23", - "2.0.M6-SNAP23a" - ).map(Version.apply) - val pairs = v.tails.flatMap { - case h :: t => t.map((h, _)) - case Nil => List.empty - } - pairs.foreach { - case (a, b) => - assert(a < b) - assert(b > a) - } - } - - 'parser - { - - Symbol("parse 1.0.5") - { - assertMatch(VersionParser.parse("1.0.5")) { - case Parsed.Success((Seq(1, 0, 5), Seq(), Seq()), _) => - } - } - - Symbol("parse 1.0.M3") - { - assertMatch(VersionParser.parse("1.0.M3")) { - case Parsed.Success((Seq(1, 0), Seq("M3"), Seq()), _) => - } - } - Symbol("parse 1.0.3m") - { - assertMatch(VersionParser.parse("1.0.3m")) { - case Parsed.Success((Seq(1, 0), Seq("3m"), Seq()), _) => - } - } - Symbol("parse 1.0.3m.4") - { - assertMatch(VersionParser.parse("1.0.3m.4")) { - case Parsed.Success((Seq(1, 0), Seq("3m", "4"), Seq()), _) => - } - } - Symbol("parse 9.1-901-1.jdbc4") - { - assertMatch(VersionParser.parse("9.1-901-1.jdbc4")) { - case Parsed.Success((Seq(9, 1), Seq("901", "1", "jdbc4"), Seq()), _) => - } - } - Symbol("parse 1.33.7+build/11.e0f985a") - { - assertMatch(VersionParser.parse("1.33.7+build/11.e0f985a")) { - case Parsed.Success((Seq(1, 33, 7), Seq(), Seq("build/11", "e0f985a")), _) => - } - } - Symbol("parse 9.1-901-1.jdbc4+build/11.e0f985a") - { - assertMatch(VersionParser.parse("9.1-901-1.jdbc4+build/11.e0f985a")) { - case Parsed.Success((Seq(9, 1), Seq("901", "1", "jdbc4"), Seq("build/11", "e0f985a")), _) => - } - } - } - } -} diff --git a/scalalib/test/src/mill/scalalib/publish/IvyTests.scala b/scalalib/test/src/mill/scalalib/publish/IvyTests.scala deleted file mode 100644 index d187f969..00000000 --- a/scalalib/test/src/mill/scalalib/publish/IvyTests.scala +++ /dev/null @@ -1,60 +0,0 @@ -package mill.scalalib.publish - -import utest._ -import mill._ - -import scala.xml.{Node, NodeSeq, XML} - -object IvyTests extends TestSuite { - - def tests: Tests = Tests { - val artifactId = "mill-scalalib_2.12" - val artifact = - Artifact("com.lihaoyi", "mill-scalalib_2.12", "0.0.1") - val deps = Agg( - Dependency(Artifact("com.lihaoyi", "mill-main_2.12", "0.1.4"), - Scope.Compile), - Dependency(Artifact("org.scala-sbt", "test-interface", "1.0"), - Scope.Compile), - Dependency(Artifact("com.lihaoyi", "pprint_2.12", "0.5.3"), - Scope.Compile, exclusions = List("com.lihaoyi" -> "fansi_2.12", "*" -> "sourcecode_2.12")) - ) - - 'fullIvy - { - val fullIvy = XML.loadString(Ivy(artifact, deps)) - - 'topLevel - { - val info = singleNode(fullIvy \ "info") - assert( - singleAttr(info, "organisation") == artifact.group, - singleAttr(info, "module") == artifact.id, - singleAttr(info, "revision") == artifact.version - ) - } - - 'dependencies - { - val dependencies = fullIvy \ "dependencies" \ "dependency" - val ivyDeps = deps.indexed - - assert(dependencies.size == ivyDeps.size) - - dependencies.zipWithIndex.foreach { case (dep, index) => - assert( - singleAttr(dep, "org") == ivyDeps(index).artifact.group, - singleAttr(dep, "name") == ivyDeps(index).artifact.id, - singleAttr(dep, "rev") == ivyDeps(index).artifact.version, - (dep \ "exclude").zipWithIndex forall { case (exclude, j) => - singleAttr(exclude, "org") == ivyDeps(index).exclusions(j)._1 && - singleAttr(exclude, "name") == ivyDeps(index).exclusions(j)._2 - } - ) - } - } - } - } - - def singleNode(seq: NodeSeq): Node = - seq.headOption.getOrElse(throw new RuntimeException("empty seq")) - def singleAttr(node: Node, attr: String): String = - node.attribute(attr).flatMap(_.headOption.map(_.text)).getOrElse(throw new RuntimeException(s"empty attr $attr")) -} diff --git a/scalalib/test/src/mill/scalalib/publish/PomTests.scala b/scalalib/test/src/mill/scalalib/publish/PomTests.scala deleted file mode 100644 index 307ae379..00000000 --- a/scalalib/test/src/mill/scalalib/publish/PomTests.scala +++ /dev/null @@ -1,205 +0,0 @@ -package mill.scalalib.publish - -import utest._ -import mill._ - -import scala.xml.{NodeSeq, XML} - -object PomTests extends TestSuite { - - def tests: Tests = Tests { - val artifactId = "mill-scalalib_2.12" - val artifact = - Artifact("com.lihaoyi", "mill-scalalib_2.12", "0.0.1") - val deps = Agg( - Dependency(Artifact("com.lihaoyi", "mill-main_2.12", "0.1.4"), - Scope.Compile), - Dependency(Artifact("org.scala-sbt", "test-interface", "1.0"), - Scope.Compile), - Dependency(Artifact("com.lihaoyi", "pprint_2.12", "0.5.3"), - Scope.Compile, exclusions = List("com.lihaoyi" -> "fansi_2.12", "*" -> "sourcecode_2.12")) - ) - val settings = PomSettings( - description = "mill-scalalib", - organization = "com.lihaoyi", - url = "https://github.com/lihaoyi/mill", - licenses = Seq(License.`MIT`), - versionControl = VersionControl.github("lihaoyi", "mill"), - developers = List( - Developer("lihaoyi", - "Li Haoyi", - "https://github.com/lihaoyi", - None, - None), - Developer("rockjam", - "Nikolai Tatarinov", - "https://github.com/rockjam", - Some("80pct done Inc."), - Some("https://80pctdone.com/")) - ) - ) - - 'fullPom - { - val fullPom = pomXml(artifact, deps, artifactId, settings) - - 'topLevel - { - assert( - singleText(fullPom \ "modelVersion") == "4.0.0", - singleText(fullPom \ "name") == artifactId, - singleText(fullPom \ "groupId") == artifact.group, - singleText(fullPom \ "artifactId") == artifact.id, - singleText(fullPom \ "packaging") == "jar", - singleText(fullPom \ "description") == settings.description, - singleText(fullPom \ "version") == artifact.version, - singleText(fullPom \ "url") == settings.url - ) - } - - 'licenses - { - val licenses = fullPom \ "licenses" \ "license" - - assert(licenses.size == 1) - - val license = licenses.head - val pomLicense = settings.licenses.head - assert( - singleText(license \ "name") == pomLicense.name, - singleText(license \ "url") == pomLicense.url, - singleText(license \ "distribution") == pomLicense.distribution - ) - } - - 'scm - { - val scm = (fullPom \ "scm").head - val pomScm = settings.versionControl - - assert( - optText(scm \ "connection") == pomScm.connection, - optText(scm \ "developerConnection") == pomScm.developerConnection, - optText(scm \ "tag").isEmpty, - optText(scm \ "url") == pomScm.browsableRepository - ) - } - - 'developers - { - val developers = fullPom \ "developers" \ "developer" - - assert(developers.size == 2) - - val pomDevelopers = settings.developers - - assert( - singleText(developers.head \ "id") == pomDevelopers.head.id, - singleText(developers.head \ "name") == pomDevelopers.head.name, - optText(developers.head \ "organization").isEmpty, - optText(developers.head \ "organizationUrl").isEmpty - ) - - assert( - singleText(developers.last \ "id") == pomDevelopers.last.id, - singleText(developers.last \ "name") == pomDevelopers.last.name, - optText(developers.last \ "organization") == pomDevelopers.last.organization, - optText(developers.last \ "organizationUrl") == pomDevelopers.last.organizationUrl - ) - } - - 'dependencies - { - val dependencies = fullPom \ "dependencies" \ "dependency" - - assert(dependencies.size == 3) - - val pomDeps = deps.indexed - - dependencies.zipWithIndex.foreach { - case (dep, index) => - assert( - singleText(dep \ "groupId") == pomDeps(index).artifact.group, - singleText(dep \ "artifactId") == pomDeps(index).artifact.id, - singleText(dep \ "version") == pomDeps(index).artifact.version, - optText(dep \ "scope").isEmpty, - (dep \ "exclusions").zipWithIndex.forall { case (node, j) => - singleText(node \ "exclusion" \ "groupId") == pomDeps(index).exclusions(j)._1 && - singleText(node \ "exclusion" \ "artifactId") == pomDeps(index).exclusions(j)._2 - } - ) - } - } - } - - 'pomEmptyScm - { - val updatedSettings = settings.copy( - versionControl = VersionControl( - browsableRepository = Some("git://github.com/lihaoyi/mill.git"), - connection = None, - developerConnection = None, - tag = None - )) - val pomEmptyScm = pomXml(artifact, deps, artifactId, updatedSettings) - - 'scm - { - val scm = (pomEmptyScm \ "scm").head - val pomScm = updatedSettings.versionControl - - assert( - optText(scm \ "connection").isEmpty, - optText(scm \ "developerConnection").isEmpty, - optText(scm \ "tag").isEmpty, - optText(scm \ "url") == pomScm.browsableRepository - ) - } - } - - 'pomNoLicenses - { - val updatedSettings = settings.copy(licenses = Seq.empty) - val pomNoLicenses = pomXml(artifact, deps, artifactId, updatedSettings) - - 'licenses - { - assert( - (pomNoLicenses \ "licenses").nonEmpty, - (pomNoLicenses \ "licenses" \ "licenses").isEmpty - ) - } - } - - 'pomNoDeps - { - val pomNoDeps = pomXml(artifact, - dependencies = Agg.empty, - artifactId = artifactId, - pomSettings = settings) - - 'dependencies - { - assert( - (pomNoDeps \ "dependencies").nonEmpty, - (pomNoDeps \ "dependencies" \ "dependency").isEmpty - ) - } - } - - 'pomNoDevelopers - { - val updatedSettings = settings.copy(developers = Seq.empty) - val pomNoDevelopers = pomXml(artifact, deps, artifactId, updatedSettings) - - 'developers - { - assert( - (pomNoDevelopers \ "developers").nonEmpty, - (pomNoDevelopers \ "developers" \ "developer").isEmpty - ) - } - } - } - - def pomXml(artifact: Artifact, - dependencies: Agg[Dependency], - artifactId: String, - pomSettings: PomSettings) = - XML.loadString(Pom(artifact, dependencies, artifactId, pomSettings)) - - def singleText(seq: NodeSeq) = - seq - .map(_.text) - .headOption - .getOrElse(throw new RuntimeException("seq was empty")) - - def optText(seq: NodeSeq) = seq.map(_.text).headOption - -} diff --git a/scalalib/test/src/mill/scalalib/scalafmt/ScalafmtTests.scala b/scalalib/test/src/mill/scalalib/scalafmt/ScalafmtTests.scala deleted file mode 100644 index dcbdb769..00000000 --- a/scalalib/test/src/mill/scalalib/scalafmt/ScalafmtTests.scala +++ /dev/null @@ -1,104 +0,0 @@ -package mill.scalalib.scalafmt - -import mill.main.Tasks -import mill.scalalib.ScalaModule -import mill.util.{TestEvaluator, TestUtil} -import utest._ -import utest.framework.TestPath - -object ScalafmtTests extends TestSuite { - - trait TestBase extends TestUtil.BaseModule { - def millSourcePath = - TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') - } - - object ScalafmtTestModule extends TestBase { - object core extends ScalaModule with ScalafmtModule { - def scalaVersion = "2.12.4" - } - } - - val resourcePath = os.pwd / 'scalalib / 'test / 'resources / 'scalafmt - - def workspaceTest[T]( - m: TestUtil.BaseModule, - resourcePath: os.Path = resourcePath)(t: TestEvaluator => T)( - implicit tp: TestPath): T = { - val eval = new TestEvaluator(m) - os.remove.all(m.millSourcePath) - os.remove.all(eval.outPath) - os.makeDir.all(m.millSourcePath / os.up) - os.copy(resourcePath, m.millSourcePath) - t(eval) - } - - def tests: Tests = Tests { - 'scalafmt - { - def checkReformat(reformatCommand: mill.define.Command[Unit]) = - workspaceTest(ScalafmtTestModule) { eval => - val before = getProjectFiles(ScalafmtTestModule.core, eval) - - // first reformat - val Right(_) = eval.apply(reformatCommand) - - val firstReformat = getProjectFiles(ScalafmtTestModule.core, eval) - - assert( - firstReformat("Main.scala").modifyTime > before("Main.scala").modifyTime, - firstReformat("Main.scala").content != before("Main.scala").content, - firstReformat("Person.scala").modifyTime > before("Person.scala").modifyTime, - firstReformat("Person.scala").content != before("Person.scala").content, - // resources files aren't modified - firstReformat("application.conf").modifyTime == before( - "application.conf").modifyTime - ) - - // cached reformat - val Right(_) = eval.apply(reformatCommand) - - val cached = getProjectFiles(ScalafmtTestModule.core, eval) - - assert( - cached("Main.scala").modifyTime == firstReformat("Main.scala").modifyTime, - cached("Person.scala").modifyTime == firstReformat("Person.scala").modifyTime, - cached("application.conf").modifyTime == firstReformat( - "application.conf").modifyTime - ) - - // reformat after change - os.write.over(cached("Main.scala").path, - cached("Main.scala").content + "\n object Foo") - - val Right(_) = eval.apply(reformatCommand) - - val afterChange = getProjectFiles(ScalafmtTestModule.core, eval) - - assert( - afterChange("Main.scala").modifyTime > cached("Main.scala").modifyTime, - afterChange("Person.scala").modifyTime == cached("Person.scala").modifyTime, - afterChange("application.conf").modifyTime == cached( - "application.conf").modifyTime - ) - } - - 'reformat - checkReformat(ScalafmtTestModule.core.reformat()) - 'reformatAll - checkReformat( - ScalafmtModule.reformatAll(Tasks(Seq(ScalafmtTestModule.core.sources)))) - } - } - - case class FileInfo(content: String, modifyTime: Long, path: os.Path) - - def getProjectFiles(m: ScalaModule, eval: TestEvaluator) = { - val Right((sources, _)) = eval.apply(m.sources) - val Right((resources, _)) = eval.apply(m.resources) - - val sourcesFiles = sources.flatMap(p => os.walk(p.path)) - val resourcesFiles = resources.flatMap(p => os.walk(p.path)) - (sourcesFiles ++ resourcesFiles).map { p => - p.last -> FileInfo(os.read(p), os.mtime(p), p) - }.toMap - } - -} diff --git a/scalalib/test/src/publish/IvyTests.scala b/scalalib/test/src/publish/IvyTests.scala new file mode 100644 index 00000000..d187f969 --- /dev/null +++ b/scalalib/test/src/publish/IvyTests.scala @@ -0,0 +1,60 @@ +package mill.scalalib.publish + +import utest._ +import mill._ + +import scala.xml.{Node, NodeSeq, XML} + +object IvyTests extends TestSuite { + + def tests: Tests = Tests { + val artifactId = "mill-scalalib_2.12" + val artifact = + Artifact("com.lihaoyi", "mill-scalalib_2.12", "0.0.1") + val deps = Agg( + Dependency(Artifact("com.lihaoyi", "mill-main_2.12", "0.1.4"), + Scope.Compile), + Dependency(Artifact("org.scala-sbt", "test-interface", "1.0"), + Scope.Compile), + Dependency(Artifact("com.lihaoyi", "pprint_2.12", "0.5.3"), + Scope.Compile, exclusions = List("com.lihaoyi" -> "fansi_2.12", "*" -> "sourcecode_2.12")) + ) + + 'fullIvy - { + val fullIvy = XML.loadString(Ivy(artifact, deps)) + + 'topLevel - { + val info = singleNode(fullIvy \ "info") + assert( + singleAttr(info, "organisation") == artifact.group, + singleAttr(info, "module") == artifact.id, + singleAttr(info, "revision") == artifact.version + ) + } + + 'dependencies - { + val dependencies = fullIvy \ "dependencies" \ "dependency" + val ivyDeps = deps.indexed + + assert(dependencies.size == ivyDeps.size) + + dependencies.zipWithIndex.foreach { case (dep, index) => + assert( + singleAttr(dep, "org") == ivyDeps(index).artifact.group, + singleAttr(dep, "name") == ivyDeps(index).artifact.id, + singleAttr(dep, "rev") == ivyDeps(index).artifact.version, + (dep \ "exclude").zipWithIndex forall { case (exclude, j) => + singleAttr(exclude, "org") == ivyDeps(index).exclusions(j)._1 && + singleAttr(exclude, "name") == ivyDeps(index).exclusions(j)._2 + } + ) + } + } + } + } + + def singleNode(seq: NodeSeq): Node = + seq.headOption.getOrElse(throw new RuntimeException("empty seq")) + def singleAttr(node: Node, attr: String): String = + node.attribute(attr).flatMap(_.headOption.map(_.text)).getOrElse(throw new RuntimeException(s"empty attr $attr")) +} diff --git a/scalalib/test/src/publish/PomTests.scala b/scalalib/test/src/publish/PomTests.scala new file mode 100644 index 00000000..307ae379 --- /dev/null +++ b/scalalib/test/src/publish/PomTests.scala @@ -0,0 +1,205 @@ +package mill.scalalib.publish + +import utest._ +import mill._ + +import scala.xml.{NodeSeq, XML} + +object PomTests extends TestSuite { + + def tests: Tests = Tests { + val artifactId = "mill-scalalib_2.12" + val artifact = + Artifact("com.lihaoyi", "mill-scalalib_2.12", "0.0.1") + val deps = Agg( + Dependency(Artifact("com.lihaoyi", "mill-main_2.12", "0.1.4"), + Scope.Compile), + Dependency(Artifact("org.scala-sbt", "test-interface", "1.0"), + Scope.Compile), + Dependency(Artifact("com.lihaoyi", "pprint_2.12", "0.5.3"), + Scope.Compile, exclusions = List("com.lihaoyi" -> "fansi_2.12", "*" -> "sourcecode_2.12")) + ) + val settings = PomSettings( + description = "mill-scalalib", + organization = "com.lihaoyi", + url = "https://github.com/lihaoyi/mill", + licenses = Seq(License.`MIT`), + versionControl = VersionControl.github("lihaoyi", "mill"), + developers = List( + Developer("lihaoyi", + "Li Haoyi", + "https://github.com/lihaoyi", + None, + None), + Developer("rockjam", + "Nikolai Tatarinov", + "https://github.com/rockjam", + Some("80pct done Inc."), + Some("https://80pctdone.com/")) + ) + ) + + 'fullPom - { + val fullPom = pomXml(artifact, deps, artifactId, settings) + + 'topLevel - { + assert( + singleText(fullPom \ "modelVersion") == "4.0.0", + singleText(fullPom \ "name") == artifactId, + singleText(fullPom \ "groupId") == artifact.group, + singleText(fullPom \ "artifactId") == artifact.id, + singleText(fullPom \ "packaging") == "jar", + singleText(fullPom \ "description") == settings.description, + singleText(fullPom \ "version") == artifact.version, + singleText(fullPom \ "url") == settings.url + ) + } + + 'licenses - { + val licenses = fullPom \ "licenses" \ "license" + + assert(licenses.size == 1) + + val license = licenses.head + val pomLicense = settings.licenses.head + assert( + singleText(license \ "name") == pomLicense.name, + singleText(license \ "url") == pomLicense.url, + singleText(license \ "distribution") == pomLicense.distribution + ) + } + + 'scm - { + val scm = (fullPom \ "scm").head + val pomScm = settings.versionControl + + assert( + optText(scm \ "connection") == pomScm.connection, + optText(scm \ "developerConnection") == pomScm.developerConnection, + optText(scm \ "tag").isEmpty, + optText(scm \ "url") == pomScm.browsableRepository + ) + } + + 'developers - { + val developers = fullPom \ "developers" \ "developer" + + assert(developers.size == 2) + + val pomDevelopers = settings.developers + + assert( + singleText(developers.head \ "id") == pomDevelopers.head.id, + singleText(developers.head \ "name") == pomDevelopers.head.name, + optText(developers.head \ "organization").isEmpty, + optText(developers.head \ "organizationUrl").isEmpty + ) + + assert( + singleText(developers.last \ "id") == pomDevelopers.last.id, + singleText(developers.last \ "name") == pomDevelopers.last.name, + optText(developers.last \ "organization") == pomDevelopers.last.organization, + optText(developers.last \ "organizationUrl") == pomDevelopers.last.organizationUrl + ) + } + + 'dependencies - { + val dependencies = fullPom \ "dependencies" \ "dependency" + + assert(dependencies.size == 3) + + val pomDeps = deps.indexed + + dependencies.zipWithIndex.foreach { + case (dep, index) => + assert( + singleText(dep \ "groupId") == pomDeps(index).artifact.group, + singleText(dep \ "artifactId") == pomDeps(index).artifact.id, + singleText(dep \ "version") == pomDeps(index).artifact.version, + optText(dep \ "scope").isEmpty, + (dep \ "exclusions").zipWithIndex.forall { case (node, j) => + singleText(node \ "exclusion" \ "groupId") == pomDeps(index).exclusions(j)._1 && + singleText(node \ "exclusion" \ "artifactId") == pomDeps(index).exclusions(j)._2 + } + ) + } + } + } + + 'pomEmptyScm - { + val updatedSettings = settings.copy( + versionControl = VersionControl( + browsableRepository = Some("git://github.com/lihaoyi/mill.git"), + connection = None, + developerConnection = None, + tag = None + )) + val pomEmptyScm = pomXml(artifact, deps, artifactId, updatedSettings) + + 'scm - { + val scm = (pomEmptyScm \ "scm").head + val pomScm = updatedSettings.versionControl + + assert( + optText(scm \ "connection").isEmpty, + optText(scm \ "developerConnection").isEmpty, + optText(scm \ "tag").isEmpty, + optText(scm \ "url") == pomScm.browsableRepository + ) + } + } + + 'pomNoLicenses - { + val updatedSettings = settings.copy(licenses = Seq.empty) + val pomNoLicenses = pomXml(artifact, deps, artifactId, updatedSettings) + + 'licenses - { + assert( + (pomNoLicenses \ "licenses").nonEmpty, + (pomNoLicenses \ "licenses" \ "licenses").isEmpty + ) + } + } + + 'pomNoDeps - { + val pomNoDeps = pomXml(artifact, + dependencies = Agg.empty, + artifactId = artifactId, + pomSettings = settings) + + 'dependencies - { + assert( + (pomNoDeps \ "dependencies").nonEmpty, + (pomNoDeps \ "dependencies" \ "dependency").isEmpty + ) + } + } + + 'pomNoDevelopers - { + val updatedSettings = settings.copy(developers = Seq.empty) + val pomNoDevelopers = pomXml(artifact, deps, artifactId, updatedSettings) + + 'developers - { + assert( + (pomNoDevelopers \ "developers").nonEmpty, + (pomNoDevelopers \ "developers" \ "developer").isEmpty + ) + } + } + } + + def pomXml(artifact: Artifact, + dependencies: Agg[Dependency], + artifactId: String, + pomSettings: PomSettings) = + XML.loadString(Pom(artifact, dependencies, artifactId, pomSettings)) + + def singleText(seq: NodeSeq) = + seq + .map(_.text) + .headOption + .getOrElse(throw new RuntimeException("seq was empty")) + + def optText(seq: NodeSeq) = seq.map(_.text).headOption + +} diff --git a/scalalib/test/src/scalafmt/ScalafmtTests.scala b/scalalib/test/src/scalafmt/ScalafmtTests.scala new file mode 100644 index 00000000..dcbdb769 --- /dev/null +++ b/scalalib/test/src/scalafmt/ScalafmtTests.scala @@ -0,0 +1,104 @@ +package mill.scalalib.scalafmt + +import mill.main.Tasks +import mill.scalalib.ScalaModule +import mill.util.{TestEvaluator, TestUtil} +import utest._ +import utest.framework.TestPath + +object ScalafmtTests extends TestSuite { + + trait TestBase extends TestUtil.BaseModule { + def millSourcePath = + TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') + } + + object ScalafmtTestModule extends TestBase { + object core extends ScalaModule with ScalafmtModule { + def scalaVersion = "2.12.4" + } + } + + val resourcePath = os.pwd / 'scalalib / 'test / 'resources / 'scalafmt + + def workspaceTest[T]( + m: TestUtil.BaseModule, + resourcePath: os.Path = resourcePath)(t: TestEvaluator => T)( + implicit tp: TestPath): T = { + val eval = new TestEvaluator(m) + os.remove.all(m.millSourcePath) + os.remove.all(eval.outPath) + os.makeDir.all(m.millSourcePath / os.up) + os.copy(resourcePath, m.millSourcePath) + t(eval) + } + + def tests: Tests = Tests { + 'scalafmt - { + def checkReformat(reformatCommand: mill.define.Command[Unit]) = + workspaceTest(ScalafmtTestModule) { eval => + val before = getProjectFiles(ScalafmtTestModule.core, eval) + + // first reformat + val Right(_) = eval.apply(reformatCommand) + + val firstReformat = getProjectFiles(ScalafmtTestModule.core, eval) + + assert( + firstReformat("Main.scala").modifyTime > before("Main.scala").modifyTime, + firstReformat("Main.scala").content != before("Main.scala").content, + firstReformat("Person.scala").modifyTime > before("Person.scala").modifyTime, + firstReformat("Person.scala").content != before("Person.scala").content, + // resources files aren't modified + firstReformat("application.conf").modifyTime == before( + "application.conf").modifyTime + ) + + // cached reformat + val Right(_) = eval.apply(reformatCommand) + + val cached = getProjectFiles(ScalafmtTestModule.core, eval) + + assert( + cached("Main.scala").modifyTime == firstReformat("Main.scala").modifyTime, + cached("Person.scala").modifyTime == firstReformat("Person.scala").modifyTime, + cached("application.conf").modifyTime == firstReformat( + "application.conf").modifyTime + ) + + // reformat after change + os.write.over(cached("Main.scala").path, + cached("Main.scala").content + "\n object Foo") + + val Right(_) = eval.apply(reformatCommand) + + val afterChange = getProjectFiles(ScalafmtTestModule.core, eval) + + assert( + afterChange("Main.scala").modifyTime > cached("Main.scala").modifyTime, + afterChange("Person.scala").modifyTime == cached("Person.scala").modifyTime, + afterChange("application.conf").modifyTime == cached( + "application.conf").modifyTime + ) + } + + 'reformat - checkReformat(ScalafmtTestModule.core.reformat()) + 'reformatAll - checkReformat( + ScalafmtModule.reformatAll(Tasks(Seq(ScalafmtTestModule.core.sources)))) + } + } + + case class FileInfo(content: String, modifyTime: Long, path: os.Path) + + def getProjectFiles(m: ScalaModule, eval: TestEvaluator) = { + val Right((sources, _)) = eval.apply(m.sources) + val Right((resources, _)) = eval.apply(m.resources) + + val sourcesFiles = sources.flatMap(p => os.walk(p.path)) + val resourcesFiles = resources.flatMap(p => os.walk(p.path)) + (sourcesFiles ++ resourcesFiles).map { p => + p.last -> FileInfo(os.read(p), os.mtime(p), p) + }.toMap + } + +} diff --git a/scalalib/worker/src/ZincWorkerImpl.scala b/scalalib/worker/src/ZincWorkerImpl.scala new file mode 100644 index 00000000..705d4682 --- /dev/null +++ b/scalalib/worker/src/ZincWorkerImpl.scala @@ -0,0 +1,284 @@ +package mill.scalalib.worker + +import java.io.File +import java.util.Optional + +import mill.api.Loose.Agg +import mill.api.PathRef +import xsbti.compile.{CompilerCache => _, FileAnalysisStore => _, ScalaInstance => _, _} +import mill.scalalib.api.Util.{isDotty, grepJar, scalaBinaryVersion} +import sbt.internal.inc._ +import sbt.internal.util.{ConsoleOut, MainAppender} +import sbt.util.LogExchange +import mill.scalalib.api.CompilationResult +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) +} + +class ZincWorkerImpl(ctx0: mill.api.Ctx, + compilerBridgeClasspath: Array[String]) extends mill.scalalib.api.ZincWorkerApi{ + private val ic = new sbt.internal.inc.IncrementalCompilerImpl() + val javaOnlyCompilers = { + // Keep the classpath as written by the user + val classpathOptions = ClasspathOptions.of(false, false, false, false, false) + + val dummyFile = new java.io.File("") + // Zinc does not have an entry point for Java-only compilation, so we need + // to make up a dummy ScalaCompiler instance. + val scalac = ZincUtil.scalaCompiler( + new ScalaInstance("", null, null, dummyFile, dummyFile, new Array(0), Some("")), null, + classpathOptions // this is used for javac too + ) + + ic.compilers( + instance = null, + classpathOptions, + None, + scalac + ) + } + + @volatile var mixedCompilersCache = Option.empty[(Long, Compilers)] + + def docJar(scalaVersion: String, + compilerBridgeSources: os.Path, + compilerClasspath: Agg[os.Path], + scalacPluginClasspath: Agg[os.Path], + args: Seq[String]) + (implicit ctx: mill.api.Ctx): Boolean = { + val compilers: Compilers = prepareCompilers( + scalaVersion, + compilerBridgeSources, + compilerClasspath, + scalacPluginClasspath + ) + val scaladocClass = compilers.scalac().scalaInstance().loader().loadClass("scala.tools.nsc.ScalaDoc") + val scaladocMethod = scaladocClass.getMethod("process", classOf[Array[String]]) + scaladocMethod.invoke(scaladocClass.newInstance(), args.toArray).asInstanceOf[Boolean] + } + /** Compile the bridge if it doesn't exist yet and return the output directory. + * TODO: Proper invalidation, see #389 + */ + def compileZincBridgeIfNeeded(scalaVersion: String, + sourcesJar: os.Path, + compilerJars: Array[File]): os.Path = { + val workingDir = ctx0.dest / scalaVersion + val compiledDest = workingDir / 'compiled + if (!os.exists(workingDir)) { + + ctx0.log.info("Compiling compiler interface...") + + os.makeDir.all(workingDir) + os.makeDir.all(compiledDest) + + val sourceFolder = mill.api.IO.unpackZip(sourcesJar)(workingDir) + val classloader = mill.api.ClassLoader.create(compilerJars.map(_.toURI.toURL), null)(ctx0) + val compilerMain = classloader.loadClass( + if (isDotty(scalaVersion)) + "dotty.tools.dotc.Main" + else + "scala.tools.nsc.Main" + ) + val argsArray = Array[String]( + "-d", compiledDest.toString, + "-classpath", (compilerJars ++ compilerBridgeClasspath).mkString(File.pathSeparator) + ) ++ os.walk(sourceFolder.path).filter(_.ext == "scala").map(_.toString) + + compilerMain.getMethod("process", classOf[Array[String]]) + .invoke(null, argsArray) + } + compiledDest + } + + + + def discoverMainClasses(compilationResult: CompilationResult)(implicit ctx: mill.api.Ctx): Seq[String] = { + def toScala[A](o: Optional[A]): Option[A] = if (o.isPresent) Some(o.get) else None + + toScala(FileAnalysisStore.binary(compilationResult.analysisFile.toIO).get()) + .map(_.getAnalysis) + .flatMap{ + case analysis: Analysis => + Some(analysis.infos.allInfos.values.map(_.getMainClasses).flatten.toSeq.sorted) + case _ => + None + } + .getOrElse(Seq.empty[String]) + } + + def compileJava(upstreamCompileOutput: Seq[CompilationResult], + sources: Agg[os.Path], + compileClasspath: Agg[os.Path], + javacOptions: Seq[String]) + (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { + compileInternal( + upstreamCompileOutput, + sources, + compileClasspath, + javacOptions, + scalacOptions = Nil, + javaOnlyCompilers + ) + } + + def compileMixed(upstreamCompileOutput: Seq[CompilationResult], + sources: Agg[os.Path], + compileClasspath: Agg[os.Path], + javacOptions: Seq[String], + scalaVersion: String, + scalacOptions: Seq[String], + compilerBridgeSources: os.Path, + compilerClasspath: Agg[os.Path], + scalacPluginClasspath: Agg[os.Path]) + (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { + val compilers: Compilers = prepareCompilers( + scalaVersion, + compilerBridgeSources, + compilerClasspath, + scalacPluginClasspath + ) + + compileInternal( + upstreamCompileOutput, + sources, + compileClasspath, + javacOptions, + scalacOptions = scalacPluginClasspath.map(jar => s"-Xplugin:${jar}").toSeq ++ scalacOptions, + compilers + ) + } + + private def prepareCompilers(scalaVersion: String, + compilerBridgeSources: os.Path, + compilerClasspath: Agg[os.Path], + scalacPluginClasspath: Agg[os.Path]) + (implicit ctx: mill.api.Ctx)= { + val combinedCompilerClasspath = compilerClasspath ++ scalacPluginClasspath + val combinedCompilerJars = combinedCompilerClasspath.toArray.map(_.toIO) + + val compilerBridge = compileZincBridgeIfNeeded( + scalaVersion, + compilerBridgeSources, + compilerClasspath.toArray.map(_.toIO) + ) + val compilerBridgeSig = os.mtime(compilerBridge) + + val compilersSig = + compilerBridgeSig + + combinedCompilerClasspath.map(p => p.toString().hashCode + os.mtime(p)).sum + + val compilers = mixedCompilersCache match { + case Some((k, v)) if k == compilersSig => v + case _ => + val compilerName = + if (isDotty(scalaVersion)) + s"dotty-compiler_${scalaBinaryVersion(scalaVersion)}" + else + "scala-compiler" + val scalaInstance = new ScalaInstance( + version = scalaVersion, + loader = mill.api.ClassLoader.create(combinedCompilerJars.map(_.toURI.toURL), null), + libraryJar = grepJar(compilerClasspath, "scala-library", scalaVersion).toIO, + compilerJar = grepJar(compilerClasspath, compilerName, scalaVersion).toIO, + allJars = combinedCompilerJars, + explicitActual = None + ) + val compilers = ic.compilers( + scalaInstance, + ClasspathOptionsUtil.boot, + None, + ZincUtil.scalaCompiler(scalaInstance, compilerBridge.toIO) + ) + mixedCompilersCache = Some((compilersSig, compilers)) + compilers + } + compilers + } + + private def compileInternal(upstreamCompileOutput: Seq[CompilationResult], + sources: Agg[os.Path], + compileClasspath: Agg[os.Path], + javacOptions: Seq[String], + scalacOptions: Seq[String], + compilers: Compilers) + (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { + os.makeDir.all(ctx.dest) + + 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 inputs = ic.inputs( + classpath = classesIODir +: compileClasspath.map(_.toIO).toArray, + sources = sources.toArray.map(_.toIO), + classesDirectory = classesIODir, + scalacOptions = scalacOptions.toArray, + javacOptions = javacOptions.toArray, + maxErrors = 10, + sourcePositionMappers = Array(), + order = CompileOrder.Mixed, + compilers = compilers, + 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)) + } + ) + + try { + val newResult = ic.compile( + in = inputs, + logger = logger + ) + + store.set( + AnalysisContents.create( + newResult.analysis(), + newResult.setup() + ) + ) + + mill.api.Result.Success(CompilationResult(zincFile, PathRef(classesDir))) + }catch{case e: CompileFailed => mill.api.Result.Failure(e.toString)} + } +} diff --git a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala deleted file mode 100644 index 705d4682..00000000 --- a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala +++ /dev/null @@ -1,284 +0,0 @@ -package mill.scalalib.worker - -import java.io.File -import java.util.Optional - -import mill.api.Loose.Agg -import mill.api.PathRef -import xsbti.compile.{CompilerCache => _, FileAnalysisStore => _, ScalaInstance => _, _} -import mill.scalalib.api.Util.{isDotty, grepJar, scalaBinaryVersion} -import sbt.internal.inc._ -import sbt.internal.util.{ConsoleOut, MainAppender} -import sbt.util.LogExchange -import mill.scalalib.api.CompilationResult -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) -} - -class ZincWorkerImpl(ctx0: mill.api.Ctx, - compilerBridgeClasspath: Array[String]) extends mill.scalalib.api.ZincWorkerApi{ - private val ic = new sbt.internal.inc.IncrementalCompilerImpl() - val javaOnlyCompilers = { - // Keep the classpath as written by the user - val classpathOptions = ClasspathOptions.of(false, false, false, false, false) - - val dummyFile = new java.io.File("") - // Zinc does not have an entry point for Java-only compilation, so we need - // to make up a dummy ScalaCompiler instance. - val scalac = ZincUtil.scalaCompiler( - new ScalaInstance("", null, null, dummyFile, dummyFile, new Array(0), Some("")), null, - classpathOptions // this is used for javac too - ) - - ic.compilers( - instance = null, - classpathOptions, - None, - scalac - ) - } - - @volatile var mixedCompilersCache = Option.empty[(Long, Compilers)] - - def docJar(scalaVersion: String, - compilerBridgeSources: os.Path, - compilerClasspath: Agg[os.Path], - scalacPluginClasspath: Agg[os.Path], - args: Seq[String]) - (implicit ctx: mill.api.Ctx): Boolean = { - val compilers: Compilers = prepareCompilers( - scalaVersion, - compilerBridgeSources, - compilerClasspath, - scalacPluginClasspath - ) - val scaladocClass = compilers.scalac().scalaInstance().loader().loadClass("scala.tools.nsc.ScalaDoc") - val scaladocMethod = scaladocClass.getMethod("process", classOf[Array[String]]) - scaladocMethod.invoke(scaladocClass.newInstance(), args.toArray).asInstanceOf[Boolean] - } - /** Compile the bridge if it doesn't exist yet and return the output directory. - * TODO: Proper invalidation, see #389 - */ - def compileZincBridgeIfNeeded(scalaVersion: String, - sourcesJar: os.Path, - compilerJars: Array[File]): os.Path = { - val workingDir = ctx0.dest / scalaVersion - val compiledDest = workingDir / 'compiled - if (!os.exists(workingDir)) { - - ctx0.log.info("Compiling compiler interface...") - - os.makeDir.all(workingDir) - os.makeDir.all(compiledDest) - - val sourceFolder = mill.api.IO.unpackZip(sourcesJar)(workingDir) - val classloader = mill.api.ClassLoader.create(compilerJars.map(_.toURI.toURL), null)(ctx0) - val compilerMain = classloader.loadClass( - if (isDotty(scalaVersion)) - "dotty.tools.dotc.Main" - else - "scala.tools.nsc.Main" - ) - val argsArray = Array[String]( - "-d", compiledDest.toString, - "-classpath", (compilerJars ++ compilerBridgeClasspath).mkString(File.pathSeparator) - ) ++ os.walk(sourceFolder.path).filter(_.ext == "scala").map(_.toString) - - compilerMain.getMethod("process", classOf[Array[String]]) - .invoke(null, argsArray) - } - compiledDest - } - - - - def discoverMainClasses(compilationResult: CompilationResult)(implicit ctx: mill.api.Ctx): Seq[String] = { - def toScala[A](o: Optional[A]): Option[A] = if (o.isPresent) Some(o.get) else None - - toScala(FileAnalysisStore.binary(compilationResult.analysisFile.toIO).get()) - .map(_.getAnalysis) - .flatMap{ - case analysis: Analysis => - Some(analysis.infos.allInfos.values.map(_.getMainClasses).flatten.toSeq.sorted) - case _ => - None - } - .getOrElse(Seq.empty[String]) - } - - def compileJava(upstreamCompileOutput: Seq[CompilationResult], - sources: Agg[os.Path], - compileClasspath: Agg[os.Path], - javacOptions: Seq[String]) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { - compileInternal( - upstreamCompileOutput, - sources, - compileClasspath, - javacOptions, - scalacOptions = Nil, - javaOnlyCompilers - ) - } - - def compileMixed(upstreamCompileOutput: Seq[CompilationResult], - sources: Agg[os.Path], - compileClasspath: Agg[os.Path], - javacOptions: Seq[String], - scalaVersion: String, - scalacOptions: Seq[String], - compilerBridgeSources: os.Path, - compilerClasspath: Agg[os.Path], - scalacPluginClasspath: Agg[os.Path]) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { - val compilers: Compilers = prepareCompilers( - scalaVersion, - compilerBridgeSources, - compilerClasspath, - scalacPluginClasspath - ) - - compileInternal( - upstreamCompileOutput, - sources, - compileClasspath, - javacOptions, - scalacOptions = scalacPluginClasspath.map(jar => s"-Xplugin:${jar}").toSeq ++ scalacOptions, - compilers - ) - } - - private def prepareCompilers(scalaVersion: String, - compilerBridgeSources: os.Path, - compilerClasspath: Agg[os.Path], - scalacPluginClasspath: Agg[os.Path]) - (implicit ctx: mill.api.Ctx)= { - val combinedCompilerClasspath = compilerClasspath ++ scalacPluginClasspath - val combinedCompilerJars = combinedCompilerClasspath.toArray.map(_.toIO) - - val compilerBridge = compileZincBridgeIfNeeded( - scalaVersion, - compilerBridgeSources, - compilerClasspath.toArray.map(_.toIO) - ) - val compilerBridgeSig = os.mtime(compilerBridge) - - val compilersSig = - compilerBridgeSig + - combinedCompilerClasspath.map(p => p.toString().hashCode + os.mtime(p)).sum - - val compilers = mixedCompilersCache match { - case Some((k, v)) if k == compilersSig => v - case _ => - val compilerName = - if (isDotty(scalaVersion)) - s"dotty-compiler_${scalaBinaryVersion(scalaVersion)}" - else - "scala-compiler" - val scalaInstance = new ScalaInstance( - version = scalaVersion, - loader = mill.api.ClassLoader.create(combinedCompilerJars.map(_.toURI.toURL), null), - libraryJar = grepJar(compilerClasspath, "scala-library", scalaVersion).toIO, - compilerJar = grepJar(compilerClasspath, compilerName, scalaVersion).toIO, - allJars = combinedCompilerJars, - explicitActual = None - ) - val compilers = ic.compilers( - scalaInstance, - ClasspathOptionsUtil.boot, - None, - ZincUtil.scalaCompiler(scalaInstance, compilerBridge.toIO) - ) - mixedCompilersCache = Some((compilersSig, compilers)) - compilers - } - compilers - } - - private def compileInternal(upstreamCompileOutput: Seq[CompilationResult], - sources: Agg[os.Path], - compileClasspath: Agg[os.Path], - javacOptions: Seq[String], - scalacOptions: Seq[String], - compilers: Compilers) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { - os.makeDir.all(ctx.dest) - - 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 inputs = ic.inputs( - classpath = classesIODir +: compileClasspath.map(_.toIO).toArray, - sources = sources.toArray.map(_.toIO), - classesDirectory = classesIODir, - scalacOptions = scalacOptions.toArray, - javacOptions = javacOptions.toArray, - maxErrors = 10, - sourcePositionMappers = Array(), - order = CompileOrder.Mixed, - compilers = compilers, - 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)) - } - ) - - try { - val newResult = ic.compile( - in = inputs, - logger = logger - ) - - store.set( - AnalysisContents.create( - newResult.analysis(), - newResult.setup() - ) - ) - - mill.api.Result.Success(CompilationResult(zincFile, PathRef(classesDir))) - }catch{case e: CompileFailed => mill.api.Result.Failure(e.toString)} - } -} -- cgit v1.2.3 From ea36ea3da18d3720e124b60235e1153f6c31518c Mon Sep 17 00:00:00 2001 From: dohrayme <960977+dohrayme@users.noreply.github.com> Date: Sat, 15 Dec 2018 02:34:31 +0000 Subject: fix GenIdea to create required folders (#510) --- scalalib/src/GenIdeaImpl.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'scalalib') diff --git a/scalalib/src/GenIdeaImpl.scala b/scalalib/src/GenIdeaImpl.scala index 2d76d804..548b8d4e 100644 --- a/scalalib/src/GenIdeaImpl.scala +++ b/scalalib/src/GenIdeaImpl.scala @@ -43,7 +43,7 @@ object GenIdeaImpl { val evaluator = new Evaluator(ctx.home, os.pwd / 'out, os.pwd / 'out, rootModule, ctx.log) for((relPath, xml) <- xmlFileLayout(evaluator, rootModule, jdkInfo)){ - os.write.over(os.pwd/relPath, pp.format(xml)) + os.write.over(os.pwd/relPath, pp.format(xml), createFolders = true) } } -- cgit v1.2.3 From de175e69977082e35539097a54d381e465dddf8e Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 18 Dec 2018 21:11:47 -0800 Subject: Generalize Zinc Worker (#514) * Generalize Zinc worker - Compiler bridges can now be either pre-compiled or on-demand-compiled - Scala library/compiler jar discovery is now configurable - Zinc compiler cache is now configurable, rather than being hardcoded at n=1 * . * update constructor args * remove duplicate util/AggWrapper.scala file * fix * fix * fix * cleanup --- ci/shared.sc | 2 +- contrib/scalapblib/src/ScalaPBModule.scala | 2 +- contrib/twirllib/src/TwirlModule.scala | 2 +- main/api/src/mill/api/KeyedLockedCache.scala | 44 +++++ main/core/src/define/Graph.scala | 2 +- main/core/src/eval/Evaluator.scala | 2 +- main/core/src/util/AggWrapper.scala | 119 ------------ main/core/src/util/MultiBiMap.scala | 2 +- main/src/main/ReplApplyHandler.scala | 2 +- main/src/main/RunScript.scala | 2 +- main/src/modules/Jvm.scala | 2 +- main/src/modules/Util.scala | 5 +- main/src/package.scala | 4 +- main/test/resources/examples/javac/build.sc | 2 +- main/test/src/define/CacherTests.scala | 2 +- main/test/src/define/GraphTests.scala | 2 +- main/test/src/eval/EvaluationTests.scala | 2 +- main/test/src/eval/JavaCompileJarTests.scala | 7 +- main/test/src/util/TestEvaluator.scala | 2 +- main/test/src/util/TestUtil.scala | 2 +- scalajslib/src/ScalaJSModule.scala | 3 +- scalalib/api/src/ZincWorkerApi.scala | 16 +- scalalib/src/GenIdeaImpl.scala | 4 +- scalalib/src/JavaModule.scala | 2 +- scalalib/src/MiscModule.scala | 2 +- scalalib/src/ScalaModule.scala | 35 +--- scalalib/src/ZincWorkerModule.scala | 59 +++++- .../src/dependency/versions/VersionsFinder.scala | 2 +- scalalib/src/publish/Ivy.scala | 2 +- scalalib/src/publish/Pom.scala | 2 +- scalalib/test/src/ResolveDepsTests.scala | 2 +- scalalib/worker/src/ZincWorkerImpl.scala | 199 +++++++++++---------- scalanativelib/src/ScalaNativeModule.scala | 2 +- 33 files changed, 248 insertions(+), 291 deletions(-) create mode 100644 main/api/src/mill/api/KeyedLockedCache.scala delete mode 100644 main/core/src/util/AggWrapper.scala (limited to 'scalalib') diff --git a/ci/shared.sc b/ci/shared.sc index 2f133486..a496fd1f 100644 --- a/ci/shared.sc +++ b/ci/shared.sc @@ -74,7 +74,7 @@ def generateEval(dir: Path) = { s"""package mill.main |import mill.eval.Evaluator |import mill.define.Task - |import mill.util.Strict.Agg + |import mill.api.Strict.Agg |class EvalGenerated(evaluator: Evaluator) { | type TT[+X] = Task[X] | ${(1 to 22).map(generate).mkString("\n")} diff --git a/contrib/scalapblib/src/ScalaPBModule.scala b/contrib/scalapblib/src/ScalaPBModule.scala index db5c5c8b..57bfdd40 100644 --- a/contrib/scalapblib/src/ScalaPBModule.scala +++ b/contrib/scalapblib/src/ScalaPBModule.scala @@ -7,7 +7,7 @@ import mill.define.Sources import mill.api.PathRef import mill.scalalib.Lib.resolveDependencies import mill.scalalib._ -import mill.util.Loose +import mill.api.Loose trait ScalaPBModule extends ScalaModule { diff --git a/contrib/twirllib/src/TwirlModule.scala b/contrib/twirllib/src/TwirlModule.scala index 328afc47..985765fc 100644 --- a/contrib/twirllib/src/TwirlModule.scala +++ b/contrib/twirllib/src/TwirlModule.scala @@ -6,7 +6,7 @@ import mill.define.Sources import mill.api.PathRef import mill.scalalib.Lib.resolveDependencies import mill.scalalib._ -import mill.util.Loose +import mill.api.Loose import scala.io.Codec import scala.util.Properties diff --git a/main/api/src/mill/api/KeyedLockedCache.scala b/main/api/src/mill/api/KeyedLockedCache.scala new file mode 100644 index 00000000..47fdd888 --- /dev/null +++ b/main/api/src/mill/api/KeyedLockedCache.scala @@ -0,0 +1,44 @@ +package mill.api + +/** + * A combination lock & cache; users provide a key, value-factory, and a + * body function to be called with the value. [[KeyedLockedCache]] ensures that + * the body function is called with the computed/cached value sequentially. + */ +trait KeyedLockedCache[T]{ + def withCachedValue[V](key: Long)(f: => T)(f2: T => V): V +} + +object KeyedLockedCache{ + class RandomBoundedCache[T](hotParallelism: Int, coldCacheSize: Int) extends KeyedLockedCache[T]{ + private[this] val random = new scala.util.Random(313373) + val available = new java.util.concurrent.Semaphore(hotParallelism) + + // Awful asymptotic complexity, but our caches are tiny n < 10 so it doesn't matter + var cache = Array.fill[Option[(Long, T)]](coldCacheSize)(None) + + def withCachedValue[V](key: Long)(f: => T)(f2: T => V): V = { + available.acquire() + val pickedValue = synchronized{ + cache.indexWhere(_.exists(_._1 == key)) match { + case -1 => f + case i => + val (k, v) = cache(i).get + cache(i) = None + v + } + } + val result = f2(pickedValue) + synchronized{ + cache.indexWhere(_.isEmpty) match{ + // Random eviction #YOLO + case -1 => cache(random.nextInt(cache.length)) = Some((key, pickedValue)) + case i => cache(i) = Some((key, pickedValue)) + } + } + + available.release() + result + } + } +} diff --git a/main/core/src/define/Graph.scala b/main/core/src/define/Graph.scala index 3119f2fb..5b29bd7b 100644 --- a/main/core/src/define/Graph.scala +++ b/main/core/src/define/Graph.scala @@ -2,7 +2,7 @@ package mill.define import mill.eval.Tarjans import mill.util.MultiBiMap -import mill.util.Strict.Agg +import mill.api.Strict.Agg object Graph { diff --git a/main/core/src/eval/Evaluator.scala b/main/core/src/eval/Evaluator.scala index 8709064e..dbaf9433 100644 --- a/main/core/src/eval/Evaluator.scala +++ b/main/core/src/eval/Evaluator.scala @@ -10,7 +10,7 @@ import mill.define.{Ctx => _, _} import mill.api.Result.OuterStack import mill.util import mill.util._ -import mill.util.Strict.Agg +import mill.api.Strict.Agg import scala.collection.mutable import scala.util.control.NonFatal diff --git a/main/core/src/util/AggWrapper.scala b/main/core/src/util/AggWrapper.scala deleted file mode 100644 index 6c107875..00000000 --- a/main/core/src/util/AggWrapper.scala +++ /dev/null @@ -1,119 +0,0 @@ -package mill.util - - - -import scala.collection.mutable -object Strict extends AggWrapper(true) -object Loose extends AggWrapper(false) -sealed class AggWrapper(strictUniqueness: Boolean){ - /** - * A collection with enforced uniqueness, fast contains and deterministic - * ordering. Raises an exception if a duplicate is found; call - * `toSeq.distinct` if you explicitly want to make it swallow duplicates - */ - trait Agg[V] extends TraversableOnce[V]{ - def contains(v: V): Boolean - def items: Iterator[V] - def indexed: IndexedSeq[V] - def flatMap[T](f: V => TraversableOnce[T]): Agg[T] - def map[T](f: V => T): Agg[T] - def filter(f: V => Boolean): Agg[V] - def withFilter(f: V => Boolean): Agg[V] - def collect[T](f: PartialFunction[V, T]): Agg[T] - def zipWithIndex: Agg[(V, Int)] - def reverse: Agg[V] - def zip[T](other: Agg[T]): Agg[(V, T)] - def ++[T >: V](other: TraversableOnce[T]): Agg[T] - def length: Int - } - - object Agg{ - def empty[V]: Agg[V] = new Agg.Mutable[V] - implicit def jsonFormat[T: upickle.default.ReadWriter]: upickle.default.ReadWriter[Agg[T]] = - upickle.default.readwriter[Seq[T]].bimap[Agg[T]]( - _.toList, - Agg.from(_) - ) - - def apply[V](items: V*) = from(items) - - implicit def from[V](items: TraversableOnce[V]): Agg[V] = { - val set = new Agg.Mutable[V]() - items.foreach(set.append) - set - } - - - class Mutable[V]() extends Agg[V]{ - - private[this] val set0 = mutable.LinkedHashSet.empty[V] - def contains(v: V) = set0.contains(v) - def append(v: V) = if (!contains(v)){ - set0.add(v) - - }else if (strictUniqueness){ - throw new Exception("Duplicated item inserted into OrderedSet: " + v) - } - def appendAll(vs: Seq[V]) = vs.foreach(append) - def items = set0.iterator - def indexed: IndexedSeq[V] = items.toIndexedSeq - def set: collection.Set[V] = set0 - - def map[T](f: V => T): Agg[T] = { - val output = new Agg.Mutable[T] - for(i <- items) output.append(f(i)) - output - } - def flatMap[T](f: V => TraversableOnce[T]): Agg[T] = { - val output = new Agg.Mutable[T] - for(i <- items) for(i0 <- f(i)) output.append(i0) - output - } - def filter(f: V => Boolean): Agg[V] = { - val output = new Agg.Mutable[V] - for(i <- items) if (f(i)) output.append(i) - output - } - def withFilter(f: V => Boolean): Agg[V] = filter(f) - - def collect[T](f: PartialFunction[V, T]) = this.filter(f.isDefinedAt).map(x => f(x)) - - def zipWithIndex = { - var i = 0 - this.map{ x => - i += 1 - (x, i-1) - } - } - - def reverse = Agg.from(indexed.reverseIterator) - - def zip[T](other: Agg[T]) = Agg.from(items.zip(other.items)) - def ++[T >: V](other: TraversableOnce[T]) = Agg.from(items ++ other) - def length: Int = set0.size - - // Members declared in scala.collection.GenTraversableOnce - def isTraversableAgain: Boolean = items.isTraversableAgain - def toIterator: Iterator[V] = items.toIterator - def toStream: Stream[V] = items.toStream - - // Members declared in scala.collection.TraversableOnce - def copyToArray[B >: V](xs: Array[B], start: Int,len: Int): Unit = items.copyToArray(xs, start, len) - def exists(p: V => Boolean): Boolean = items.exists(p) - def find(p: V => Boolean): Option[V] = items.find(p) - def forall(p: V => Boolean): Boolean = items.forall(p) - def foreach[U](f: V => U): Unit = items.foreach(f) - def hasDefiniteSize: Boolean = items.hasDefiniteSize - def isEmpty: Boolean = items.isEmpty - def seq: scala.collection.TraversableOnce[V] = items - def toTraversable: Traversable[V] = items.toTraversable - - override def hashCode() = items.map(_.hashCode()).sum - override def equals(other: Any) = other match{ - case s: Agg[_] => items.sameElements(s.items) - case _ => super.equals(other) - } - override def toString = items.mkString("Agg(", ", ", ")") - } - } -} diff --git a/main/core/src/util/MultiBiMap.scala b/main/core/src/util/MultiBiMap.scala index 73bb42c4..51ea63f2 100644 --- a/main/core/src/util/MultiBiMap.scala +++ b/main/core/src/util/MultiBiMap.scala @@ -1,7 +1,7 @@ package mill.util import scala.collection.mutable -import Strict.Agg +import mill.api.Strict.Agg /** * A map from keys to collections of values: you can assign multiple values diff --git a/main/src/main/ReplApplyHandler.scala b/main/src/main/ReplApplyHandler.scala index a8e467d4..786a1409 100644 --- a/main/src/main/ReplApplyHandler.scala +++ b/main/src/main/ReplApplyHandler.scala @@ -6,7 +6,7 @@ import mill.define.Segment.Label import mill.define._ import mill.eval.{Evaluator, Result} -import mill.util.Strict.Agg +import mill.api.Strict.Agg import scala.collection.mutable object ReplApplyHandler{ diff --git a/main/src/main/RunScript.scala b/main/src/main/RunScript.scala index 47526631..b858c8b9 100644 --- a/main/src/main/RunScript.scala +++ b/main/src/main/RunScript.scala @@ -11,7 +11,7 @@ import mill.define._ import mill.eval.{Evaluator, PathRef, Result} import mill.util.{EitherOps, ParseArgs, Watched} import mill.api.Logger -import mill.util.Strict.Agg +import mill.api.Strict.Agg import scala.collection.mutable import scala.reflect.ClassTag diff --git a/main/src/modules/Jvm.scala b/main/src/modules/Jvm.scala index 1a51ed8b..e17631e3 100644 --- a/main/src/modules/Jvm.scala +++ b/main/src/modules/Jvm.scala @@ -15,7 +15,7 @@ import mill.main.client.InputPumper import mill.eval.{PathRef, Result} import mill.util.Ctx import mill.api.IO -import mill.util.Loose.Agg +import mill.api.Loose.Agg import scala.collection.mutable import scala.collection.JavaConverters._ diff --git a/main/src/modules/Util.scala b/main/src/modules/Util.scala index 2b98a304..029626fe 100644 --- a/main/src/modules/Util.scala +++ b/main/src/modules/Util.scala @@ -3,7 +3,8 @@ package mill.modules import coursier.Repository import mill.api.{PathRef, IO} -import mill.util.{Ctx, Loose} +import mill.util.Ctx +import mill.api.Loose object Util { @@ -55,7 +56,7 @@ object Util { val localPath = sys.props(key) if (localPath != null) { mill.api.Result.Success( - Loose.Agg.from(localPath.split(',').map(p => PathRef(os.Path(p), quick = true))) + mill.api.Loose.Agg.from(localPath.split(',').map(p => PathRef(os.Path(p), quick = true))) ) } else { mill.modules.Jvm.resolveDependencies( diff --git a/main/src/package.scala b/main/src/package.scala index 0ccd094f..6bcb1bdf 100644 --- a/main/src/package.scala +++ b/main/src/package.scala @@ -7,6 +7,6 @@ package object mill extends JsonFormatters{ type PathRef = mill.api.PathRef type Module = define.Module type Cross[T] = define.Cross[T] - type Agg[T] = util.Loose.Agg[T] - val Agg = util.Loose.Agg + type Agg[T] = mill.api.Loose.Agg[T] + val Agg = mill.api.Loose.Agg } diff --git a/main/test/resources/examples/javac/build.sc b/main/test/resources/examples/javac/build.sc index 2ed9f915..17366219 100644 --- a/main/test/resources/examples/javac/build.sc +++ b/main/test/resources/examples/javac/build.sc @@ -2,7 +2,7 @@ import mill.T import mill.eval.JavaCompileJarTests.compileAll import mill.api.PathRef import mill.modules.Jvm -import mill.util.Loose +import mill.api.Loose def sourceRootPath = millSourcePath / 'src def resourceRootPath = millSourcePath / 'resources diff --git a/main/test/src/define/CacherTests.scala b/main/test/src/define/CacherTests.scala index 59ebf3f6..1524e5c1 100644 --- a/main/test/src/define/CacherTests.scala +++ b/main/test/src/define/CacherTests.scala @@ -1,7 +1,7 @@ package mill.define import mill.util.{DummyLogger, TestEvaluator, TestUtil} -import mill.util.Strict.Agg +import mill.api.Strict.Agg import mill.T import mill.api.Result.Success import utest._ diff --git a/main/test/src/define/GraphTests.scala b/main/test/src/define/GraphTests.scala index 224ce59f..b36dbf95 100644 --- a/main/test/src/define/GraphTests.scala +++ b/main/test/src/define/GraphTests.scala @@ -4,7 +4,7 @@ package mill.define import mill.eval.Evaluator import mill.util.{TestGraphs, TestUtil} import utest._ -import mill.util.Strict.Agg +import mill.api.Strict.Agg object GraphTests extends TestSuite{ val tests = Tests{ diff --git a/main/test/src/eval/EvaluationTests.scala b/main/test/src/eval/EvaluationTests.scala index 74f9088c..7f924db2 100644 --- a/main/test/src/eval/EvaluationTests.scala +++ b/main/test/src/eval/EvaluationTests.scala @@ -5,7 +5,7 @@ import mill.util.TestUtil.{Test, test} import mill.define.{Discover, Graph, Target, Task} import mill.{Module, T} import mill.util.{DummyLogger, TestEvaluator, TestGraphs, TestUtil} -import mill.util.Strict.Agg +import mill.api.Strict.Agg import utest._ import utest.framework.TestPath diff --git a/main/test/src/eval/JavaCompileJarTests.scala b/main/test/src/eval/JavaCompileJarTests.scala index 426c6ea6..0f9002df 100644 --- a/main/test/src/eval/JavaCompileJarTests.scala +++ b/main/test/src/eval/JavaCompileJarTests.scala @@ -4,12 +4,13 @@ import mill.define.{Discover, Input, Target, Task} import mill.modules.Jvm import mill.api.Ctx.Dest import mill.{Module, T} -import mill.util.{DummyLogger, Loose, TestEvaluator, TestUtil} -import mill.util.Strict.Agg +import mill.util.{DummyLogger, TestEvaluator, TestUtil} +import mill.api.Strict.Agg +import mill.api.Loose import utest._ import mill._ object JavaCompileJarTests extends TestSuite{ - def compileAll(sources: mill.util.Loose.Agg[PathRef])(implicit ctx: Dest) = { + def compileAll(sources: mill.api.Loose.Agg[PathRef])(implicit ctx: Dest) = { os.makeDir.all(ctx.dest) os.proc("javac", sources.map(_.path.toString()).toSeq, "-d", ctx.dest).call(ctx.dest) diff --git a/main/test/src/util/TestEvaluator.scala b/main/test/src/util/TestEvaluator.scala index 9a235679..81c8fe12 100644 --- a/main/test/src/util/TestEvaluator.scala +++ b/main/test/src/util/TestEvaluator.scala @@ -3,7 +3,7 @@ package mill.util import mill.define.{Input, Target, Task} import mill.api.Result.OuterStack import mill.eval.{Evaluator, Result} -import mill.util.Strict.Agg +import mill.api.Strict.Agg import utest.assert import utest.framework.TestPath diff --git a/main/test/src/util/TestUtil.scala b/main/test/src/util/TestUtil.scala index baab2992..462b7f5c 100644 --- a/main/test/src/util/TestUtil.scala +++ b/main/test/src/util/TestUtil.scala @@ -5,7 +5,7 @@ import mill.define._ import mill.api.Result import mill.api.Result.OuterStack import utest.assert -import mill.util.Strict.Agg +import mill.api.Strict.Agg import utest.framework.TestPath import scala.collection.mutable diff --git a/scalajslib/src/ScalaJSModule.scala b/scalajslib/src/ScalaJSModule.scala index 8568c39b..137e8ee2 100644 --- a/scalajslib/src/ScalaJSModule.scala +++ b/scalajslib/src/ScalaJSModule.scala @@ -7,7 +7,8 @@ import mill.eval.{PathRef, Result} import mill.api.Result.Success import mill.scalalib.Lib.resolveDependencies import mill.scalalib.{DepSyntax, Lib, TestModule, TestRunner} -import mill.util.{Ctx, Loose} +import mill.util.Ctx +import mill.api.Loose import mill.scalajslib.api._ trait ScalaJSModule extends scalalib.ScalaModule { outer => diff --git a/scalalib/api/src/ZincWorkerApi.scala b/scalalib/api/src/ZincWorkerApi.scala index c5230ec5..d42be9f3 100644 --- a/scalalib/api/src/ZincWorkerApi.scala +++ b/scalalib/api/src/ZincWorkerApi.scala @@ -3,14 +3,16 @@ package mill.scalalib.api import mill.api.Loose.Agg import mill.api.PathRef import mill.api.JsonFormatters._ - +object ZincWorkerApi{ + type Ctx = mill.api.Ctx.Dest with mill.api.Ctx.Log with mill.api.Ctx.Home +} trait ZincWorkerApi { /** Compile a Java-only project */ def compileJava(upstreamCompileOutput: Seq[CompilationResult], sources: Agg[os.Path], compileClasspath: Agg[os.Path], javacOptions: Seq[String]) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] + (implicit ctx: ZincWorkerApi.Ctx): mill.api.Result[CompilationResult] /** Compile a mixed Scala/Java or Scala-only project */ def compileMixed(upstreamCompileOutput: Seq[CompilationResult], @@ -18,21 +20,21 @@ trait ZincWorkerApi { compileClasspath: Agg[os.Path], javacOptions: Seq[String], scalaVersion: String, + scalaOrganization: String, scalacOptions: Seq[String], - compilerBridgeSources: os.Path, compilerClasspath: Agg[os.Path], scalacPluginClasspath: Agg[os.Path]) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] + (implicit ctx: ZincWorkerApi.Ctx): mill.api.Result[CompilationResult] def discoverMainClasses(compilationResult: CompilationResult) - (implicit ctx: mill.api.Ctx): Seq[String] + (implicit ctx: ZincWorkerApi.Ctx): Seq[String] def docJar(scalaVersion: String, - compilerBridgeSources: os.Path, + scalaOrganization: String, compilerClasspath: Agg[os.Path], scalacPluginClasspath: Agg[os.Path], args: Seq[String]) - (implicit ctx: mill.api.Ctx): Boolean + (implicit ctx: ZincWorkerApi.Ctx): Boolean } diff --git a/scalalib/src/GenIdeaImpl.scala b/scalalib/src/GenIdeaImpl.scala index 548b8d4e..b8f9d35e 100644 --- a/scalalib/src/GenIdeaImpl.scala +++ b/scalalib/src/GenIdeaImpl.scala @@ -5,8 +5,8 @@ import coursier.{Cache, CoursierPaths, Repository} import mill.define._ import mill.eval.{Evaluator, PathRef, Result} import mill.api.Ctx.{Home, Log} -import mill.util.Strict.Agg -import mill.util.{Loose, Strict} +import mill.api.Strict.Agg +import mill.api.{Loose, Strict} import mill.{T, scalalib} import scala.util.Try diff --git a/scalalib/src/JavaModule.scala b/scalalib/src/JavaModule.scala index 78be8893..72c0a5a6 100644 --- a/scalalib/src/JavaModule.scala +++ b/scalalib/src/JavaModule.scala @@ -9,7 +9,7 @@ import mill.modules.{Assembly, Jvm} import mill.modules.Jvm.{createAssembly, createJar} import Lib._ import mill.scalalib.publish.{Artifact, Scope} -import mill.util.Loose.Agg +import mill.api.Loose.Agg /** * Core configuration required to compile a single Scala compilation target diff --git a/scalalib/src/MiscModule.scala b/scalalib/src/MiscModule.scala index c6449d6e..bf64f1f3 100644 --- a/scalalib/src/MiscModule.scala +++ b/scalalib/src/MiscModule.scala @@ -4,7 +4,7 @@ package scalalib import mill.define.Cross.Resolver import mill.define.{Cross, Task} import mill.eval.{PathRef, Result} -import mill.util.Loose.Agg +import mill.api.Loose.Agg object CrossModuleBase{ def scalaVersionPaths(scalaVersion: String, f: String => os.Path) = { for(segments <- scalaVersion.split('.').inits.filter(_.nonEmpty)) diff --git a/scalalib/src/ScalaModule.scala b/scalalib/src/ScalaModule.scala index 9d669bf4..5fad1664 100644 --- a/scalalib/src/ScalaModule.scala +++ b/scalalib/src/ScalaModule.scala @@ -8,7 +8,7 @@ import mill.modules.Jvm import mill.modules.Jvm.createJar import mill.scalalib.api.Util.isDotty import Lib._ -import mill.util.Loose.Agg +import mill.api.Loose.Agg import mill.api.DummyInputStream /** @@ -79,36 +79,7 @@ trait ScalaModule extends JavaModule { outer => def scalaDocOptions = T{ scalacOptions() } - private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r - def scalaCompilerBridgeSources = T { - val (scalaVersion0, scalaBinaryVersion0) = scalaVersion() match { - case Milestone213(_, _) => ("2.13.0-M2", "2.13.0-M2") - case _ => (scalaVersion(), mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion())) - } - - val (bridgeDep, bridgeName, bridgeVersion) = - if (isDotty(scalaVersion0)) { - val org = scalaOrganization() - val name = "dotty-sbt-bridge" - val version = scalaVersion() - (ivy"$org:$name:$version", name, version) - } else { - val org = "org.scala-sbt" - val name = "compiler-bridge" - val version = Versions.zinc - (ivy"$org::$name:$version", s"${name}_$scalaBinaryVersion0", version) - } - - resolveDependencies( - repositories, - Lib.depToDependency(_, scalaVersion0, platformSuffix()), - Seq(bridgeDep), - sources = true - ).map(deps => - mill.scalalib.api.Util.grepJar(deps.map(_.path), bridgeName, bridgeVersion, sources = true) - ) - } /** * The local classpath of Scala compiler plugins on-disk; you can add @@ -159,8 +130,8 @@ trait ScalaModule extends JavaModule { outer => compileClasspath().map(_.path), javacOptions(), scalaVersion(), + scalaOrganization(), scalacOptions(), - scalaCompilerBridgeSources(), scalaCompilerClasspath().map(_.path), scalacPluginClasspath().map(_.path), ) @@ -187,7 +158,7 @@ trait ScalaModule extends JavaModule { outer => else { zincWorker.worker().docJar( scalaVersion(), - scalaCompilerBridgeSources(), + scalaOrganization(), scalaCompilerClasspath().map(_.path), scalacPluginClasspath().map(_.path), files ++ options diff --git a/scalalib/src/ZincWorkerModule.scala b/scalalib/src/ZincWorkerModule.scala index 5ca824ce..97d84aaf 100644 --- a/scalalib/src/ZincWorkerModule.scala +++ b/scalalib/src/ZincWorkerModule.scala @@ -4,9 +4,12 @@ import coursier.Cache import coursier.maven.MavenRepository import mill.Agg import mill.T +import mill.api.KeyedLockedCache import mill.define.{Discover, Worker} import mill.scalalib.Lib.resolveDependencies -import mill.util.Loose +import mill.scalalib.api.Util.isDotty +import mill.scalalib.api.ZincWorkerApi +import mill.api.Loose import mill.util.JsonFormatters._ object ZincWorkerModule extends mill.define.ExternalModule with ZincWorkerModule{ @@ -40,11 +43,61 @@ trait ZincWorkerModule extends mill.Module{ getClass.getClassLoader ) val cls = cl.loadClass("mill.scalalib.worker.ZincWorkerImpl") - val instance = cls.getConstructor(classOf[mill.api.Ctx], classOf[Array[String]]) - .newInstance(T.ctx(), compilerInterfaceClasspath().map(_.path.toString).toArray[String]) + val instance = cls.getConstructor( + classOf[ + Either[ + (ZincWorkerApi.Ctx, Array[os.Path], (String, String) => os.Path), + String => os.Path + ] + ], + classOf[(Agg[os.Path], String) => os.Path], + classOf[(Agg[os.Path], String) => os.Path], + classOf[KeyedLockedCache[_]] + ) + .newInstance( + Left(( + T.ctx(), + compilerInterfaceClasspath().map(_.path).toArray, + (x: String, y: String) => scalaCompilerBridgeSourceJar(x, y).asSuccess.get.value + )), + mill.scalalib.api.Util.grepJar(_, "scala-library", _, sources = false), + mill.scalalib.api.Util.grepJar(_, "scala-compiler", _, sources = false), + new KeyedLockedCache.RandomBoundedCache(1, 1) + ) instance.asInstanceOf[mill.scalalib.api.ZincWorkerApi] } + private val Milestone213 = raw"""2.13.(\d+)-M(\d+)""".r + def scalaCompilerBridgeSourceJar(scalaVersion: String, + scalaOrganization: String) = { + val (scalaVersion0, scalaBinaryVersion0) = scalaVersion match { + case Milestone213(_, _) => ("2.13.0-M2", "2.13.0-M2") + case _ => (scalaVersion, mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion)) + } + + val (bridgeDep, bridgeName, bridgeVersion) = + if (isDotty(scalaVersion0)) { + val org = scalaOrganization + val name = "dotty-sbt-bridge" + val version = scalaVersion + (ivy"$org:$name:$version", name, version) + } else { + val org = "org.scala-sbt" + val name = "compiler-bridge" + val version = Versions.zinc + (ivy"$org::$name:$version", s"${name}_$scalaBinaryVersion0", version) + } + + resolveDependencies( + repositories, + Lib.depToDependency(_, scalaVersion0, ""), + Seq(bridgeDep), + sources = true + ).map(deps => + mill.scalalib.api.Util.grepJar(deps.map(_.path), bridgeName, bridgeVersion, sources = true) + ) + } + def compilerInterfaceClasspath = T{ resolveDependencies( repositories, diff --git a/scalalib/src/dependency/versions/VersionsFinder.scala b/scalalib/src/dependency/versions/VersionsFinder.scala index a831ffc3..a9ecc763 100644 --- a/scalalib/src/dependency/versions/VersionsFinder.scala +++ b/scalalib/src/dependency/versions/VersionsFinder.scala @@ -5,7 +5,7 @@ import mill.eval.Evaluator import mill.scalalib.dependency.metadata.MetadataLoaderFactory import mill.scalalib.{Dep, JavaModule, Lib} import mill.api.Ctx.{Home, Log} -import mill.util.{Loose, Strict} +import mill.api.{Loose, Strict} private[dependency] object VersionsFinder { diff --git a/scalalib/src/publish/Ivy.scala b/scalalib/src/publish/Ivy.scala index 22e26ff6..e06efadd 100644 --- a/scalalib/src/publish/Ivy.scala +++ b/scalalib/src/publish/Ivy.scala @@ -1,6 +1,6 @@ package mill.scalalib.publish -import mill.util.Loose.Agg +import mill.api.Loose.Agg import scala.xml.PrettyPrinter diff --git a/scalalib/src/publish/Pom.scala b/scalalib/src/publish/Pom.scala index 57a0e196..a7f1f6fc 100644 --- a/scalalib/src/publish/Pom.scala +++ b/scalalib/src/publish/Pom.scala @@ -1,6 +1,6 @@ package mill.scalalib.publish -import mill.util.Loose.Agg +import mill.api.Loose.Agg import scala.xml.{Atom, Elem, NodeSeq, PrettyPrinter} diff --git a/scalalib/test/src/ResolveDepsTests.scala b/scalalib/test/src/ResolveDepsTests.scala index 78361625..ce905907 100644 --- a/scalalib/test/src/ResolveDepsTests.scala +++ b/scalalib/test/src/ResolveDepsTests.scala @@ -4,7 +4,7 @@ import coursier.Cache import coursier.maven.MavenRepository import mill.api.Result.{Failure, Success} import mill.eval.{PathRef, Result} -import mill.util.Loose.Agg +import mill.api.Loose.Agg import utest._ object ResolveDepsTests extends TestSuite { diff --git a/scalalib/worker/src/ZincWorkerImpl.scala b/scalalib/worker/src/ZincWorkerImpl.scala index 705d4682..c37ef162 100644 --- a/scalalib/worker/src/ZincWorkerImpl.scala +++ b/scalalib/worker/src/ZincWorkerImpl.scala @@ -4,13 +4,13 @@ import java.io.File import java.util.Optional import mill.api.Loose.Agg -import mill.api.PathRef +import mill.api.{KeyedLockedCache, PathRef} import xsbti.compile.{CompilerCache => _, FileAnalysisStore => _, ScalaInstance => _, _} -import mill.scalalib.api.Util.{isDotty, grepJar, scalaBinaryVersion} +import mill.scalalib.api.Util.{grepJar, isDotty, scalaBinaryVersion} import sbt.internal.inc._ import sbt.internal.util.{ConsoleOut, MainAppender} import sbt.util.LogExchange -import mill.scalalib.api.CompilationResult +import mill.scalalib.api.{CompilationResult, ZincWorkerApi} case class MockedLookup(am: File => Optional[CompileAnalysis]) extends PerClasspathEntryLookup { override def analysis(classpathEntry: File): Optional[CompileAnalysis] = am(classpathEntry) @@ -19,10 +19,16 @@ case class MockedLookup(am: File => Optional[CompileAnalysis]) extends PerClassp Locate.definesClass(classpathEntry) } -class ZincWorkerImpl(ctx0: mill.api.Ctx, - compilerBridgeClasspath: Array[String]) extends mill.scalalib.api.ZincWorkerApi{ +class ZincWorkerImpl(compilerBridge: Either[ + (ZincWorkerApi.Ctx, Array[os.Path], (String, String) => os.Path), + String => os.Path + ], + libraryJarNameGrep: (Agg[os.Path], String) => os.Path, + compilerJarNameGrep: (Agg[os.Path], String) => os.Path, + compilerCache: KeyedLockedCache[Compilers]) + extends ZincWorkerApi{ private val ic = new sbt.internal.inc.IncrementalCompilerImpl() - val javaOnlyCompilers = { + lazy val javaOnlyCompilers = { // Keep the classpath as written by the user val classpathOptions = ClasspathOptions.of(false, false, false, false, false) @@ -42,68 +48,68 @@ class ZincWorkerImpl(ctx0: mill.api.Ctx, ) } - @volatile var mixedCompilersCache = Option.empty[(Long, Compilers)] - def docJar(scalaVersion: String, - compilerBridgeSources: os.Path, + scalaOrganization: String, compilerClasspath: Agg[os.Path], scalacPluginClasspath: Agg[os.Path], args: Seq[String]) - (implicit ctx: mill.api.Ctx): Boolean = { - val compilers: Compilers = prepareCompilers( + (implicit ctx: ZincWorkerApi.Ctx): Boolean = { + withCompilers( scalaVersion, - compilerBridgeSources, + scalaOrganization, compilerClasspath, - scalacPluginClasspath - ) - val scaladocClass = compilers.scalac().scalaInstance().loader().loadClass("scala.tools.nsc.ScalaDoc") - val scaladocMethod = scaladocClass.getMethod("process", classOf[Array[String]]) - scaladocMethod.invoke(scaladocClass.newInstance(), args.toArray).asInstanceOf[Boolean] + scalacPluginClasspath, + ) { compilers: Compilers => + val scaladocClass = compilers.scalac().scalaInstance().loader().loadClass("scala.tools.nsc.ScalaDoc") + val scaladocMethod = scaladocClass.getMethod("process", classOf[Array[String]]) + scaladocMethod.invoke(scaladocClass.newInstance(), args.toArray).asInstanceOf[Boolean] + } } /** Compile the bridge if it doesn't exist yet and return the output directory. - * TODO: Proper invalidation, see #389 - */ - def compileZincBridgeIfNeeded(scalaVersion: String, - sourcesJar: os.Path, - compilerJars: Array[File]): os.Path = { - val workingDir = ctx0.dest / scalaVersion - val compiledDest = workingDir / 'compiled - if (!os.exists(workingDir)) { - - ctx0.log.info("Compiling compiler interface...") - - os.makeDir.all(workingDir) - os.makeDir.all(compiledDest) - - val sourceFolder = mill.api.IO.unpackZip(sourcesJar)(workingDir) - val classloader = mill.api.ClassLoader.create(compilerJars.map(_.toURI.toURL), null)(ctx0) - val compilerMain = classloader.loadClass( - if (isDotty(scalaVersion)) - "dotty.tools.dotc.Main" - else - "scala.tools.nsc.Main" - ) - val argsArray = Array[String]( - "-d", compiledDest.toString, - "-classpath", (compilerJars ++ compilerBridgeClasspath).mkString(File.pathSeparator) - ) ++ os.walk(sourceFolder.path).filter(_.ext == "scala").map(_.toString) - - compilerMain.getMethod("process", classOf[Array[String]]) - .invoke(null, argsArray) + * TODO: Proper invalidation, see #389 + */ + def compileZincBridgeIfNeeded(scalaVersion: String, scalaOrganization: String, compilerJars: Array[File]): os.Path = { + compilerBridge match{ + case Right(compiled) => compiled(scalaVersion) + case Left((ctx0, compilerBridgeClasspath, srcJars)) => + val workingDir = ctx0.dest / scalaVersion + val compiledDest = workingDir / 'compiled + if (!os.exists(workingDir)) { + ctx0.log.info("Compiling compiler interface...") + + os.makeDir.all(workingDir) + os.makeDir.all(compiledDest) + + val sourceFolder = mill.api.IO.unpackZip(srcJars(scalaVersion, scalaOrganization))(workingDir) + val classloader = mill.api.ClassLoader.create(compilerJars.map(_.toURI.toURL), null)(ctx0) + val compilerMain = classloader.loadClass( + if (isDotty(scalaVersion)) "dotty.tools.dotc.Main" + else "scala.tools.nsc.Main" + ) + val argsArray = Array[String]( + "-d", compiledDest.toString, + "-classpath", (compilerJars ++ compilerBridgeClasspath).mkString(File.pathSeparator) + ) ++ os.walk(sourceFolder.path).filter(_.ext == "scala").map(_.toString) + + compilerMain.getMethod("process", classOf[Array[String]]) + .invoke(null, argsArray) + } + compiledDest } - compiledDest + } - def discoverMainClasses(compilationResult: CompilationResult)(implicit ctx: mill.api.Ctx): Seq[String] = { + def discoverMainClasses(compilationResult: CompilationResult) + (implicit ctx: ZincWorkerApi.Ctx): Seq[String] = { def toScala[A](o: Optional[A]): Option[A] = if (o.isPresent) Some(o.get) else None toScala(FileAnalysisStore.binary(compilationResult.analysisFile.toIO).get()) .map(_.getAnalysis) .flatMap{ case analysis: Analysis => - Some(analysis.infos.allInfos.values.map(_.getMainClasses).flatten.toSeq.sorted) + Some(analysis.infos.allInfos.values.flatMap(_.getMainClasses).toSeq.sorted) case _ => None } @@ -114,7 +120,7 @@ class ZincWorkerImpl(ctx0: mill.api.Ctx, sources: Agg[os.Path], compileClasspath: Agg[os.Path], javacOptions: Seq[String]) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { + (implicit ctx: ZincWorkerApi.Ctx): mill.api.Result[CompilationResult] = { compileInternal( upstreamCompileOutput, sources, @@ -130,73 +136,70 @@ class ZincWorkerImpl(ctx0: mill.api.Ctx, compileClasspath: Agg[os.Path], javacOptions: Seq[String], scalaVersion: String, + scalaOrganization: String, scalacOptions: Seq[String], - compilerBridgeSources: os.Path, compilerClasspath: Agg[os.Path], scalacPluginClasspath: Agg[os.Path]) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { - val compilers: Compilers = prepareCompilers( + (implicit ctx: ZincWorkerApi.Ctx): mill.api.Result[CompilationResult] = { + withCompilers( scalaVersion, - compilerBridgeSources, + scalaOrganization, compilerClasspath, - scalacPluginClasspath - ) - - compileInternal( - upstreamCompileOutput, - sources, - compileClasspath, - javacOptions, - scalacOptions = scalacPluginClasspath.map(jar => s"-Xplugin:${jar}").toSeq ++ scalacOptions, - compilers - ) + scalacPluginClasspath, + ) {compilers: Compilers => + compileInternal( + upstreamCompileOutput, + sources, + compileClasspath, + javacOptions, + scalacOptions = scalacPluginClasspath.map(jar => s"-Xplugin:$jar").toSeq ++ scalacOptions, + compilers + ) + } } - private def prepareCompilers(scalaVersion: String, - compilerBridgeSources: os.Path, + private def withCompilers[T](scalaVersion: String, + scalaOrganization: String, compilerClasspath: Agg[os.Path], scalacPluginClasspath: Agg[os.Path]) - (implicit ctx: mill.api.Ctx)= { + (f: Compilers => T) + (implicit ctx: ZincWorkerApi.Ctx)= { val combinedCompilerClasspath = compilerClasspath ++ scalacPluginClasspath val combinedCompilerJars = combinedCompilerClasspath.toArray.map(_.toIO) - val compilerBridge = compileZincBridgeIfNeeded( + val compiledCompilerBridge = compileZincBridgeIfNeeded( scalaVersion, - compilerBridgeSources, + scalaOrganization, compilerClasspath.toArray.map(_.toIO) ) - val compilerBridgeSig = os.mtime(compilerBridge) + + val compilerBridgeSig = os.mtime(compiledCompilerBridge) val compilersSig = compilerBridgeSig + combinedCompilerClasspath.map(p => p.toString().hashCode + os.mtime(p)).sum - val compilers = mixedCompilersCache match { - case Some((k, v)) if k == compilersSig => v - case _ => - val compilerName = - if (isDotty(scalaVersion)) - s"dotty-compiler_${scalaBinaryVersion(scalaVersion)}" - else - "scala-compiler" - val scalaInstance = new ScalaInstance( - version = scalaVersion, - loader = mill.api.ClassLoader.create(combinedCompilerJars.map(_.toURI.toURL), null), - libraryJar = grepJar(compilerClasspath, "scala-library", scalaVersion).toIO, - compilerJar = grepJar(compilerClasspath, compilerName, scalaVersion).toIO, - allJars = combinedCompilerJars, - explicitActual = None - ) - val compilers = ic.compilers( - scalaInstance, - ClasspathOptionsUtil.boot, - None, - ZincUtil.scalaCompiler(scalaInstance, compilerBridge.toIO) - ) - mixedCompilersCache = Some((compilersSig, compilers)) - compilers - } - compilers + compilerCache.withCachedValue(compilersSig){ + val compilerJar = + if (isDotty(scalaVersion)) + grepJar(compilerClasspath, s"dotty-compiler_${scalaBinaryVersion(scalaVersion)}", scalaVersion) + else + compilerJarNameGrep(compilerClasspath, scalaVersion) + val scalaInstance = new ScalaInstance( + version = scalaVersion, + loader = mill.api.ClassLoader.create(combinedCompilerJars.map(_.toURI.toURL), null), + libraryJar = libraryJarNameGrep(compilerClasspath, scalaVersion).toIO, + compilerJar = compilerJar.toIO, + allJars = combinedCompilerJars, + explicitActual = None + ) + ic.compilers( + scalaInstance, + ClasspathOptionsUtil.boot, + None, + ZincUtil.scalaCompiler(scalaInstance, compiledCompilerBridge.toIO) + ) + }(f) } private def compileInternal(upstreamCompileOutput: Seq[CompilationResult], @@ -205,7 +208,7 @@ class ZincWorkerImpl(ctx0: mill.api.Ctx, javacOptions: Seq[String], scalacOptions: Seq[String], compilers: Compilers) - (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { + (implicit ctx: ZincWorkerApi.Ctx): mill.api.Result[CompilationResult] = { os.makeDir.all(ctx.dest) val logger = { diff --git a/scalanativelib/src/ScalaNativeModule.scala b/scalanativelib/src/ScalaNativeModule.scala index a7a2b96b..289ba759 100644 --- a/scalanativelib/src/ScalaNativeModule.scala +++ b/scalanativelib/src/ScalaNativeModule.scala @@ -9,7 +9,7 @@ import mill.define.{Target, Task} import mill.api.Result import mill.modules.Jvm import mill.scalalib.{Dep, DepSyntax, Lib, SbtModule, ScalaModule, TestModule, TestRunner} -import mill.util.Loose.Agg +import mill.api.Loose.Agg import sbt.testing.{AnnotatedFingerprint, SubclassFingerprint} import sbt.testing.Fingerprint import upickle.default.{ReadWriter => RW, macroRW} -- cgit v1.2.3