From c09a8a71bef49391b5e8379c48286e600ad725b9 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sat, 18 Feb 2023 18:30:12 +0100 Subject: [PATCH 1/3] Move `JarManifest` to `mill.api` Kept deprecated forwarders in `mill.scalalib.Jvm` to maintain source compatibility. --- build.sc | 15 +++++ main/api/src/mill/api/JarManifest.scala | 67 ++++++++++++++++++++ main/api/src/mill/api/JarOps.scala | 37 ++++++++++- main/src/mill/modules/Jvm.scala | 62 ++++-------------- main/test/src/eval/JavaCompileJarTests.scala | 5 +- scalalib/src/JavaModule.scala | 4 +- scalalib/src/PublishModule.scala | 4 +- scalalib/src/ScalaModule.scala | 4 +- 8 files changed, 138 insertions(+), 60 deletions(-) create mode 100644 main/api/src/mill/api/JarManifest.scala diff --git a/build.sc b/build.sc index 3e285b0254b..d15e9ab3eb0 100644 --- a/build.sc +++ b/build.sc @@ -359,6 +359,21 @@ object main extends MillModule { Deps.upickle, Deps.sbtTestInterface ) + def generatedBuildInfo: T[Seq[PathRef]] = T { + val dest = T.dest + val code = + s"""package mill.main.api + | + |/** Generated at built-time by Mill. */ + |object BuildInfo { + | /** Mill version. */ + | val millVersion: String = "${millVersion()}" + |} + |""".stripMargin + os.write(dest / "mill" / "main" / "api" / "BuildInfo.scala", code, createFolders = true) + Seq(PathRef(dest)) + } + override def generatedSources: T[Seq[PathRef]] = super.generatedSources() ++ generatedBuildInfo() } object util extends MillApiModule with MillAutoTestSetup { override def moduleDeps = Seq(api) diff --git a/main/api/src/mill/api/JarManifest.scala b/main/api/src/mill/api/JarManifest.scala new file mode 100644 index 00000000000..52803ee9c4c --- /dev/null +++ b/main/api/src/mill/api/JarManifest.scala @@ -0,0 +1,67 @@ +package mill.api + +import mill.main.api.BuildInfo +import upickle.default.ReadWriter + +import java.util.jar.{Attributes, Manifest} + +/** + * Represents a JAR manifest. + * + * @param main the main manifest attributes + * @param groups additional attributes for named entries + */ +final class JarManifest private ( + val main: Map[String, String], + val groups: Map[String, Map[String, String]] +) { + def add(entries: (String, String)*): JarManifest = copy(main = main ++ entries) + + def addGroup(group: String, entries: (String, String)*): JarManifest = + copy(groups = groups + (group -> (groups.getOrElse(group, Map.empty) ++ entries))) + + private def copy( + main: Map[String, String] = main, + groups: Map[String, Map[String, String]] = groups + ): JarManifest = JarManifest(main, groups) + + override def toString: String = Seq( + "main" -> main, + "groups" -> groups + ).map(p => s"${p._1}=${p._2}").mkString(getClass().getSimpleName + "(", ",", ")") + + /** Constructs a [[java.util.jar.Manifest]] from this JarManifest. */ + def build: Manifest = { + val manifest = new Manifest + val mainAttributes = manifest.getMainAttributes + main.foreach { case (key, value) => mainAttributes.putValue(key, value) } + val entries = manifest.getEntries + for ((group, attribs) <- groups) { + val attrib = new Attributes + attribs.foreach { case (key, value) => attrib.putValue(key, value) } + entries.put(group, attrib) + } + manifest + } +} + +object JarManifest { + + final val Empty = JarManifest() + + final val MillDefault = JarManifest( + main = Map[String, String]( + java.util.jar.Attributes.Name.MANIFEST_VERSION.toString -> "1.0", + "Created-By" -> s"Mill ${BuildInfo.millVersion}", + "Tool" -> s"Mill-${BuildInfo.millVersion}" + ) + ) + + def apply( + main: Map[String, String] = Map.empty, + groups: Map[String, Map[String, String]] = Map.empty + ): JarManifest = new JarManifest(main, groups) + + implicit val jarManifestRW: ReadWriter[JarManifest] = upickle.default.macroRW + +} diff --git a/main/api/src/mill/api/JarOps.scala b/main/api/src/mill/api/JarOps.scala index ca36c376d89..4263ce09b00 100644 --- a/main/api/src/mill/api/JarOps.scala +++ b/main/api/src/mill/api/JarOps.scala @@ -28,13 +28,46 @@ trait JarOps { def jar( jar: os.Path, inputPaths: Agg[os.Path], - manifest: Manifest, + manifest: JarManifest = JarManifest.Empty, fileFilter: (os.Path, os.RelPath) => Boolean = (_, _) => true, includeDirs: Boolean = false, timestamp: Option[Long] = None - ): Unit = { + ): Unit = this.jar( + jar = jar, + inputPaths = inputPaths, + manifest = manifest.build, + fileFilter = fileFilter, + includeDirs = includeDirs, + timestamp = timestamp + ) + /** + * Create a JAR file with default inflation level. + * d + * + * @param jar The final JAR file + * @param inputPaths The input paths resembling the content of the JAR file. + * Files will be directly included in the root of the archive, + * whereas for directories their content is added to the root of the archive. + * @param manifest The JAR Manifest + * @param fileFilter A filter to support exclusions of selected files + * @param includeDirs If `true` the JAR archive will contain directory entries. + * According to the ZIP specification, directory entries are not required. + * In the Java ecosystem, most JARs have directory entries, so including them may reduce compatibility issues. + * Directory entry names will result with a trailing `/`. + * @param timestamp If specified, this timestamp is used as modification timestamp (mtime) for all entries in the JAR file. + * Having a stable timestamp may result in reproducible files, if all other content, including the JAR Manifest, keep stable. + */ + def jar( + jar: os.Path, + inputPaths: Agg[os.Path], + manifest: Manifest, + fileFilter: (os.Path, os.RelPath) => Boolean, + includeDirs: Boolean, + timestamp: Option[Long] + ): Unit = { val curTime = timestamp.getOrElse(System.currentTimeMillis()) + def mTime(file: os.Path) = timestamp.getOrElse(os.mtime(file)) os.makeDir.all(jar / os.up) diff --git a/main/src/mill/modules/Jvm.scala b/main/src/mill/modules/Jvm.scala index 827d9ee06c7..58729e6a8ac 100644 --- a/main/src/mill/modules/Jvm.scala +++ b/main/src/mill/modules/Jvm.scala @@ -242,17 +242,10 @@ object Jvm extends CoursierSupport { ) } - def createManifest(mainClass: Option[String]): JarManifest = { - val main = - Map[String, String]( - java.util.jar.Attributes.Name.MANIFEST_VERSION.toString -> "1.0", - "Created-By" -> s"Mill ${BuildInfo.millVersion}", - "Tool" -> s"Mill-${BuildInfo.millVersion}" - ) ++ - mainClass.map(mc => Map(java.util.jar.Attributes.Name.MAIN_CLASS.toString -> mc)).getOrElse( - Map.empty - ) - JarManifest(main) + def createManifest(mainClass: Option[String]): mill.api.JarManifest = { + mainClass.foldLeft(mill.api.JarManifest.MillDefault)((m, c) => + m.add((java.util.jar.Attributes.Name.MAIN_CLASS.toString, c)) + ) } /** @@ -269,7 +262,7 @@ object Jvm extends CoursierSupport { */ def createJar( inputPaths: Agg[os.Path], - manifest: JarManifest = JarManifest.Default, + manifest: mill.api.JarManifest = mill.api.JarManifest.MillDefault, fileFilter: (os.Path, os.RelPath) => Boolean = (_, _) => true )(implicit ctx: Ctx.Dest): PathRef = { val outputPath = ctx.dest / "out.jar" @@ -285,16 +278,16 @@ object Jvm extends CoursierSupport { def createJar( jar: os.Path, inputPaths: Agg[os.Path], - manifest: JarManifest, + manifest: mill.api.JarManifest, fileFilter: (os.Path, os.RelPath) => Boolean ): Unit = - JarOps.jar(jar, inputPaths, manifest.build, fileFilter, includeDirs = true, timestamp = None) + JarOps.jar(jar, inputPaths, manifest, fileFilter, includeDirs = true, timestamp = None) def createClasspathPassingJar(jar: os.Path, classpath: Agg[os.Path]): Unit = { createJar( jar = jar, inputPaths = Agg(), - manifest = JarManifest.Default.add( + manifest = mill.api.JarManifest.MillDefault.add( "Class-Path" -> classpath.iterator.map(_.toNIO.toUri().toURL().toExternalForm()).mkString( " " ) @@ -305,7 +298,7 @@ object Jvm extends CoursierSupport { def createAssembly( inputPaths: Agg[os.Path], - manifest: JarManifest = JarManifest.Default, + manifest: mill.api.JarManifest = mill.api.JarManifest.MillDefault, prependShellScript: String = "", base: Option[os.Path] = None, assemblyRules: Seq[Assembly.Rule] = Assembly.defaultRules @@ -449,38 +442,9 @@ object Jvm extends CoursierSupport { PathRef(outputPath) } - - object JarManifest { - implicit val jarManifestRW: RW[JarManifest] = upickle.default.macroRW - final val Default = createManifest(None) - } - - /** - * Represents a JAR manifest. - * @param main the main manifest attributes - * @param groups additional attributes for named entries - */ - final case class JarManifest( - main: Map[String, String] = Map.empty, - groups: Map[String, Map[String, String]] = Map.empty - ) { - def add(entries: (String, String)*): JarManifest = copy(main = main ++ entries) - def addGroup(group: String, entries: (String, String)*): JarManifest = - copy(groups = groups + (group -> (groups.getOrElse(group, Map.empty) ++ entries))) - - /** Constructs a [[java.util.jar.Manifest]] from this JarManifest. */ - def build: Manifest = { - val manifest = new Manifest - val mainAttributes = manifest.getMainAttributes - main.foreach { case (key, value) => mainAttributes.putValue(key, value) } - val entries = manifest.getEntries - for ((group, attribs) <- groups) { - val attrib = new Attributes - attribs.foreach { case (key, value) => attrib.putValue(key, value) } - entries.put(group, attrib) - } - manifest - } - } + @deprecated("Use mill.api.JarManifest instead", "Mill after 0.11.0-M4") + type JarManifest = mill.api.JarManifest + @deprecated("Use mill.api.JarManifest instead", "Mill after 0.11.0-M4") + val JarManifest = mill.api.JarManifest } diff --git a/main/test/src/eval/JavaCompileJarTests.scala b/main/test/src/eval/JavaCompileJarTests.scala index 809191a74a0..9839929413a 100644 --- a/main/test/src/eval/JavaCompileJarTests.scala +++ b/main/test/src/eval/JavaCompileJarTests.scala @@ -2,12 +2,11 @@ package mill.eval import mill.define.{Discover, Input, Target, Task} import mill.modules.Jvm -import mill.modules.Jvm.JarManifest import mill.api.Ctx.Dest import mill.{Module, T} import mill.util.{DummyLogger, TestEvaluator, TestUtil} import mill.api.Strict.Agg -import mill.api.Loose +import mill.api.{JarManifest, Loose} import utest._ import mill._ @@ -50,7 +49,7 @@ object JavaCompileJarTests extends TestSuite { def filterJar(fileFilter: (os.Path, os.RelPath) => Boolean) = T { Jvm.createJar( Loose.Agg(classFiles().path, readme().path) ++ resourceRoot().map(_.path), - JarManifest.Default, + JarManifest.MillDefault, fileFilter ) } diff --git a/scalalib/src/JavaModule.scala b/scalalib/src/JavaModule.scala index d2e3f5e5961..f306fa8d6b3 100644 --- a/scalalib/src/JavaModule.scala +++ b/scalalib/src/JavaModule.scala @@ -10,7 +10,7 @@ import coursier.parse.ModuleParser import coursier.util.ModuleMatcher import mainargs.Flag import mill.api.Loose.Agg -import mill.api.{PathRef, Result, internal} +import mill.api.{JarManifest, PathRef, Result, internal} import mill.define.{Command, Sources, Target, Task, TaskModule} import mill.eval.EvaluatorPathsResolver import mill.modules.{Assembly, Jvm} @@ -398,7 +398,7 @@ trait JavaModule * Creates a manifest representation which can be modified or replaced * The default implementation just adds the `Manifest-Version`, `Main-Class` and `Created-By` attributes */ - def manifest: T[Jvm.JarManifest] = T { + def manifest: T[JarManifest] = T { Jvm.createManifest(finalMainClassOpt().toOption) } diff --git a/scalalib/src/PublishModule.scala b/scalalib/src/PublishModule.scala index 5d6589237ac..3f3b68a3479 100644 --- a/scalalib/src/PublishModule.scala +++ b/scalalib/src/PublishModule.scala @@ -2,7 +2,7 @@ package mill package scalalib import mill.define.{Command, ExternalModule, Target, Task} -import mill.api.{PathRef, Result} +import mill.api.{JarManifest, PathRef, Result} import mill.main.Tasks import mill.modules.Jvm import mill.scalalib.PublishModule.checkSonatypeCreds @@ -199,7 +199,7 @@ trait PublishModule extends JavaModule { outer => ).publish(artifacts.map { case (a, b) => (a.path, b) }, artifactInfo, release) } - override def manifest: T[Jvm.JarManifest] = T { + override def manifest: T[JarManifest] = T { import java.util.jar.Attributes.Name val pom = pomSettings() super.manifest().add( diff --git a/scalalib/src/ScalaModule.scala b/scalalib/src/ScalaModule.scala index 7adb465caed..3d9fdc7dd1c 100644 --- a/scalalib/src/ScalaModule.scala +++ b/scalalib/src/ScalaModule.scala @@ -3,7 +3,7 @@ package scalalib import scala.annotation.nowarn import mill.define.{Command, Sources, Target, Task} -import mill.api.{DummyInputStream, PathRef, Result, internal} +import mill.api.{DummyInputStream, JarManifest, PathRef, Result, internal} import mill.modules.Jvm import mill.modules.Jvm.createJar import mill.api.Loose.Agg @@ -501,7 +501,7 @@ trait ScalaModule extends JavaModule { outer => } } - override def manifest: T[Jvm.JarManifest] = T { + override def manifest: T[JarManifest] = T { super.manifest().add("Scala-Version" -> scalaVersion()) } From d47125c4c657ec3f24ea5a5cd295f2fff29869dc Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sat, 18 Feb 2023 22:03:19 +0100 Subject: [PATCH 2/3] Made BuildInfo private --- build.sc | 4 ++-- main/api/src/mill/api/JarManifest.scala | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/build.sc b/build.sc index d15e9ab3eb0..0eef4609d68 100644 --- a/build.sc +++ b/build.sc @@ -362,10 +362,10 @@ object main extends MillModule { def generatedBuildInfo: T[Seq[PathRef]] = T { val dest = T.dest val code = - s"""package mill.main.api + s"""package mill.api | |/** Generated at built-time by Mill. */ - |object BuildInfo { + |private[api] object BuildInfo { | /** Mill version. */ | val millVersion: String = "${millVersion()}" |} diff --git a/main/api/src/mill/api/JarManifest.scala b/main/api/src/mill/api/JarManifest.scala index 52803ee9c4c..84b040bcf80 100644 --- a/main/api/src/mill/api/JarManifest.scala +++ b/main/api/src/mill/api/JarManifest.scala @@ -1,6 +1,5 @@ package mill.api -import mill.main.api.BuildInfo import upickle.default.ReadWriter import java.util.jar.{Attributes, Manifest} From 4e4728e5fa683d481bbc46d77f03995cc517f25c Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sat, 18 Feb 2023 22:11:30 +0100 Subject: [PATCH 3/3] Fixed misplaced character --- main/api/src/mill/api/JarOps.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main/api/src/mill/api/JarOps.scala b/main/api/src/mill/api/JarOps.scala index 4263ce09b00..c2c867a40e7 100644 --- a/main/api/src/mill/api/JarOps.scala +++ b/main/api/src/mill/api/JarOps.scala @@ -11,7 +11,7 @@ trait JarOps { /** * Create a JAR file with default inflation level. - * d + * * @param jar The final JAR file * @param inputPaths The input paths resembling the content of the JAR file. * Files will be directly included in the root of the archive, @@ -43,7 +43,6 @@ trait JarOps { /** * Create a JAR file with default inflation level. - * d * * @param jar The final JAR file * @param inputPaths The input paths resembling the content of the JAR file.