diff --git a/cli/src/main/scala-2.12/coursier/cli/Bootstrap.scala b/cli/src/main/scala-2.12/coursier/cli/Bootstrap.scala index 6e2072a1e..0de4519c3 100644 --- a/cli/src/main/scala-2.12/coursier/cli/Bootstrap.scala +++ b/cli/src/main/scala-2.12/coursier/cli/Bootstrap.scala @@ -1,16 +1,17 @@ package coursier package cli -import java.io.{ByteArrayInputStream, ByteArrayOutputStream, File, FileInputStream, IOException} +import java.io.{ByteArrayInputStream, ByteArrayOutputStream, File, FileInputStream, FileOutputStream, IOException} import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.Files import java.nio.file.attribute.PosixFilePermission import java.util.Properties +import java.util.jar.{JarFile, Attributes => JarAttributes} import java.util.zip.{ZipEntry, ZipInputStream, ZipOutputStream} import caseapp._ import coursier.cli.options.BootstrapOptions -import coursier.cli.util.Zip +import coursier.cli.util.{Assembly, Zip} import coursier.internal.FileUtil import scala.collection.JavaConverters._ @@ -224,6 +225,52 @@ object Bootstrap extends CaseApp[BootstrapOptions] { ) } + private def defaultRules = Seq( + Assembly.Rule.Append("reference.conf"), + Assembly.Rule.AppendPattern("META-INF/services/.*"), + Assembly.Rule.Exclude("log4j.properties"), + Assembly.Rule.Exclude(JarFile.MANIFEST_NAME), + Assembly.Rule.ExcludePattern("META-INF/.*\\.[sS][fF]"), + Assembly.Rule.ExcludePattern("META-INF/.*\\.[dD][sS][aA]"), + Assembly.Rule.ExcludePattern("META-INF/.*\\.[rR][sS][aA]") + ) + + private def createAssemblyJar( + options: BootstrapOptions, + files: Seq[File], + javaOpts: Seq[String], + mainClass: String, + output: File + ): Unit = { + + val parsedRules = options.options.rule.map { s => + s.split(":", 2) match { + case Array("append", v) => Assembly.Rule.Append(v) + case Array("append-pattern", v) => Assembly.Rule.AppendPattern(v) + case Array("exclude", v) => Assembly.Rule.Exclude(v) + case Array("exclude-pattern", v) => Assembly.Rule.ExcludePattern(v) + case _ => + sys.error(s"Malformed assembly rule: $s") + } + } + + val rules = + (if (options.options.defaultRules) defaultRules else Nil) ++ parsedRules + + val attrs = Seq( + JarAttributes.Name.MAIN_CLASS -> mainClass + ) + + val baos = new ByteArrayOutputStream + Assembly.make(files, baos, attrs, rules) + + createJarBootstrap( + javaOpts, + output, + baos.toByteArray + ) + } + def run(options: BootstrapOptions, args: RemainingArgs): Unit = { val helper = new Helper( @@ -262,6 +309,9 @@ object Bootstrap extends CaseApp[BootstrapOptions] { } } + val javaOpts = options.options.javaOpt ++ + properties0.map { case (k, v) => s"-D$k=$v" } + val (urls, files) = helper.fetchMap( sources = false, @@ -269,21 +319,23 @@ object Bootstrap extends CaseApp[BootstrapOptions] { artifactTypes = options.artifactOptions.artifactTypes(sources = false, javadoc = false) ).toList.foldLeft((List.empty[String], List.empty[File])){ case ((urls, files), (url, file)) => - if (options.options.standalone) (urls, file :: files) + if (options.options.assembly || options.options.standalone) (urls, file :: files) else if (url.startsWith("file:/")) (urls, file :: files) else (url :: urls, files) } - createOneJarLikeJarBootstrap( - options, - helper, - mainClass, - options.options.javaOpt ++ - properties0.map { case (k, v) => s"-D$k=$v" }, - urls, - files, - output0 - ) + if (options.options.assembly) + createAssemblyJar(options, files, javaOpts, mainClass, output0) + else + createOneJarLikeJarBootstrap( + options, + helper, + mainClass, + javaOpts, + urls, + files, + output0 + ) } } diff --git a/cli/src/main/scala-2.12/coursier/cli/options/BootstrapSpecificOptions.scala b/cli/src/main/scala-2.12/coursier/cli/options/BootstrapSpecificOptions.scala index ba6241bc6..4ee172c77 100644 --- a/cli/src/main/scala-2.12/coursier/cli/options/BootstrapSpecificOptions.scala +++ b/cli/src/main/scala-2.12/coursier/cli/options/BootstrapSpecificOptions.scala @@ -29,6 +29,15 @@ final case class BootstrapSpecificOptions( target: String = "native-target", @Help("Don't wipe native compilation target directory (for debug purposes)") keepTarget: Boolean = false, + @Help("Generate an assembly rather than a bootstrap jar") + @Short("a") + assembly: Boolean = false, + @Help("Add assembly rule") + @Value("append:$path|append-pattern:$pattern|exclude:$path|exclude-pattern:$pattern") + @Short("R") + rule: List[String] = Nil, + @Help("Add default rules to assembly rule list") + defaultRules: Boolean = true, @Recurse isolated: IsolatedLoaderOptions = IsolatedLoaderOptions(), @Recurse diff --git a/scripts/travis.sh b/scripts/travis.sh index 496145c24..2a8ee09ce 100755 --- a/scripts/travis.sh +++ b/scripts/travis.sh @@ -149,6 +149,19 @@ testBootstrap() { echo -e "Error: unexpected output from bootstrapped props command.\n$OUT" 1>&2 exit 1 fi + + # assembly tests + ./coursier-test bootstrap -a -o cs-props-assembly -D other=thing -J -Dfoo=baz io.get-coursier:props:1.0.2 + local OUT="$(./cs-props-assembly foo)" + if [ "$OUT" != baz ]; then + echo -e "Error: unexpected output from assembly props command.\n$OUT" 1>&2 + exit 1 + fi + local OUT="$(./cs-props-assembly other)" + if [ "$OUT" != thing ]; then + echo -e "Error: unexpected output from assembly props command.\n$OUT" 1>&2 + exit 1 + fi fi }