mirror of https://github.com/sbt/sbt.git
Move things around in cli module
This commit is contained in:
parent
ace927da5b
commit
d4b2549c13
|
|
@ -0,0 +1,436 @@
|
||||||
|
package coursier
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import java.io.{ FileInputStream, ByteArrayOutputStream, File, IOException }
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.nio.file.{ Files => NIOFiles }
|
||||||
|
import java.nio.file.attribute.PosixFilePermission
|
||||||
|
import java.util.Properties
|
||||||
|
import java.util.zip.{ ZipEntry, ZipOutputStream, ZipInputStream }
|
||||||
|
|
||||||
|
import caseapp.{ HelpMessage => Help, ValueDescription => Value, ExtraName => Short, _ }
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
import scala.language.reflectiveCalls
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
|
||||||
|
sealed abstract class CoursierCommand extends Command
|
||||||
|
|
||||||
|
case class Resolve(
|
||||||
|
@Recurse
|
||||||
|
common: CommonOptions
|
||||||
|
) extends CoursierCommand {
|
||||||
|
|
||||||
|
// the `val helper = ` part is needed because of DelayedInit it seems
|
||||||
|
val helper = new Helper(common, remainingArgs, printResultStdout = true)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Fetch(
|
||||||
|
@Recurse
|
||||||
|
options: FetchOptions
|
||||||
|
) extends CoursierCommand {
|
||||||
|
|
||||||
|
val helper = new Helper(options.common, remainingArgs, ignoreErrors = options.force)
|
||||||
|
|
||||||
|
val files0 = helper.fetch(sources = options.sources, javadoc = options.javadoc)
|
||||||
|
|
||||||
|
val out =
|
||||||
|
if (options.classpath)
|
||||||
|
files0
|
||||||
|
.map(_.toString)
|
||||||
|
.mkString(File.pathSeparator)
|
||||||
|
else
|
||||||
|
files0
|
||||||
|
.map(_.toString)
|
||||||
|
.mkString("\n")
|
||||||
|
|
||||||
|
println(out)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object Launch {
|
||||||
|
|
||||||
|
@tailrec
|
||||||
|
def mainClassLoader(cl: ClassLoader): Option[ClassLoader] =
|
||||||
|
if (cl == null)
|
||||||
|
None
|
||||||
|
else {
|
||||||
|
val isMainLoader = try {
|
||||||
|
val cl0 = cl.asInstanceOf[Object {
|
||||||
|
def isBootstrapLoader: Boolean
|
||||||
|
}]
|
||||||
|
|
||||||
|
cl0.isBootstrapLoader
|
||||||
|
} catch {
|
||||||
|
case e: Exception =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMainLoader)
|
||||||
|
Some(cl)
|
||||||
|
else
|
||||||
|
mainClassLoader(cl.getParent)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Launch(
|
||||||
|
@Recurse
|
||||||
|
options: LaunchOptions
|
||||||
|
) extends CoursierCommand {
|
||||||
|
|
||||||
|
val (rawDependencies, extraArgs) = {
|
||||||
|
val idxOpt = Some(remainingArgs.indexOf("--")).filter(_ >= 0)
|
||||||
|
idxOpt.fold((remainingArgs, Seq.empty[String])) { idx =>
|
||||||
|
val (l, r) = remainingArgs.splitAt(idx)
|
||||||
|
assert(r.nonEmpty)
|
||||||
|
(l, r.tail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val helper = new Helper(
|
||||||
|
options.common,
|
||||||
|
rawDependencies ++ options.isolated.rawIsolated.map { case (_, dep) => dep }
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val files0 = helper.fetch(sources = false, javadoc = false)
|
||||||
|
|
||||||
|
val contextLoader = Thread.currentThread().getContextClassLoader
|
||||||
|
|
||||||
|
val parentLoader0: ClassLoader =
|
||||||
|
if (Try(contextLoader.loadClass("coursier.cli.Launch")).isSuccess)
|
||||||
|
Launch.mainClassLoader(contextLoader)
|
||||||
|
.flatMap(cl => Option(cl.getParent))
|
||||||
|
.getOrElse {
|
||||||
|
if (options.common.verbose0 >= 0)
|
||||||
|
Console.err.println(
|
||||||
|
"Warning: cannot find the main ClassLoader that launched coursier. " +
|
||||||
|
"Was coursier launched by its main launcher? " +
|
||||||
|
"The ClassLoader of the application that is about to be launched will be intertwined " +
|
||||||
|
"with the one of coursier, which may be a problem if their dependencies conflict."
|
||||||
|
)
|
||||||
|
contextLoader
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// proguarded -> no risk of conflicts, no need to find a specific ClassLoader
|
||||||
|
contextLoader
|
||||||
|
|
||||||
|
val (parentLoader, filteredFiles) =
|
||||||
|
if (options.isolated.isolated.isEmpty)
|
||||||
|
(parentLoader0, files0)
|
||||||
|
else {
|
||||||
|
val (isolatedLoader, filteredFiles0) = options.isolated.targets.foldLeft((parentLoader0, files0)) {
|
||||||
|
case ((parent, files0), target) =>
|
||||||
|
|
||||||
|
// FIXME These were already fetched above
|
||||||
|
val isolatedFiles = helper.fetch(
|
||||||
|
sources = false,
|
||||||
|
javadoc = false,
|
||||||
|
subset = options.isolated.isolatedDeps.getOrElse(target, Seq.empty).toSet
|
||||||
|
)
|
||||||
|
|
||||||
|
if (options.common.verbose0 >= 1) {
|
||||||
|
Console.err.println(s"Isolated loader files:")
|
||||||
|
for (f <- isolatedFiles.map(_.toString).sorted)
|
||||||
|
Console.err.println(s" $f")
|
||||||
|
}
|
||||||
|
|
||||||
|
val isolatedLoader = new IsolatedClassLoader(
|
||||||
|
isolatedFiles.map(_.toURI.toURL).toArray,
|
||||||
|
parent,
|
||||||
|
Array(target)
|
||||||
|
)
|
||||||
|
|
||||||
|
val filteredFiles0 = files0.filterNot(isolatedFiles.toSet)
|
||||||
|
|
||||||
|
(isolatedLoader, filteredFiles0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.common.verbose0 >= 1) {
|
||||||
|
Console.err.println(s"Remaining files:")
|
||||||
|
for (f <- filteredFiles0.map(_.toString).sorted)
|
||||||
|
Console.err.println(s" $f")
|
||||||
|
}
|
||||||
|
|
||||||
|
(isolatedLoader, filteredFiles0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val loader = new URLClassLoader(
|
||||||
|
filteredFiles.map(_.toURI.toURL).toArray,
|
||||||
|
parentLoader
|
||||||
|
)
|
||||||
|
|
||||||
|
val mainClass0 =
|
||||||
|
if (options.mainClass.nonEmpty) options.mainClass
|
||||||
|
else {
|
||||||
|
val mainClasses = Helper.mainClasses(loader)
|
||||||
|
|
||||||
|
val mainClass =
|
||||||
|
if (mainClasses.isEmpty) {
|
||||||
|
Helper.errPrintln("No main class found. Specify one with -M or --main.")
|
||||||
|
sys.exit(255)
|
||||||
|
} else if (mainClasses.size == 1) {
|
||||||
|
val (_, mainClass) = mainClasses.head
|
||||||
|
mainClass
|
||||||
|
} else {
|
||||||
|
// Trying to get the main class of the first artifact
|
||||||
|
val mainClassOpt = for {
|
||||||
|
(module, _, _) <- helper.moduleVersionConfigs.headOption
|
||||||
|
mainClass <- mainClasses.collectFirst {
|
||||||
|
case ((org, name), mainClass)
|
||||||
|
if org == module.organization && (
|
||||||
|
module.name == name ||
|
||||||
|
module.name.startsWith(name + "_") // Ignore cross version suffix
|
||||||
|
) =>
|
||||||
|
mainClass
|
||||||
|
}
|
||||||
|
} yield mainClass
|
||||||
|
|
||||||
|
mainClassOpt.getOrElse {
|
||||||
|
Helper.errPrintln(s"Cannot find default main class. Specify one with -M or --main.")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mainClass
|
||||||
|
}
|
||||||
|
|
||||||
|
val cls =
|
||||||
|
try loader.loadClass(mainClass0)
|
||||||
|
catch { case e: ClassNotFoundException =>
|
||||||
|
Helper.errPrintln(s"Error: class $mainClass0 not found")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
val method =
|
||||||
|
try cls.getMethod("main", classOf[Array[String]])
|
||||||
|
catch { case e: NoSuchMethodException =>
|
||||||
|
Helper.errPrintln(s"Error: method main not found in $mainClass0")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.common.verbose0 >= 1)
|
||||||
|
Helper.errPrintln(s"Launching $mainClass0 ${extraArgs.mkString(" ")}")
|
||||||
|
else if (options.common.verbose0 == 0)
|
||||||
|
Helper.errPrintln(s"Launching")
|
||||||
|
|
||||||
|
Thread.currentThread().setContextClassLoader(loader)
|
||||||
|
method.invoke(null, extraArgs.toArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Bootstrap(
|
||||||
|
@Recurse
|
||||||
|
options: BootstrapOptions
|
||||||
|
) extends CoursierCommand {
|
||||||
|
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
if (options.mainClass.isEmpty) {
|
||||||
|
Console.err.println(s"Error: no main class specified. Specify one with -M or --main")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.standalone && options.downloadDir.isEmpty) {
|
||||||
|
Console.err.println(s"Error: no download dir specified. Specify one with -D or --download-dir")
|
||||||
|
Console.err.println("E.g. -D \"\\$HOME/.app-name/jars\"")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
val (validProperties, wrongProperties) = options.property.partition(_.contains("="))
|
||||||
|
if (wrongProperties.nonEmpty) {
|
||||||
|
Console.err.println(s"Wrong -P / --property option(s):\n${wrongProperties.mkString("\n")}")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
val properties0 = validProperties.map { s =>
|
||||||
|
val idx = s.indexOf('=')
|
||||||
|
assert(idx >= 0)
|
||||||
|
(s.take(idx), s.drop(idx + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
val bootstrapJar =
|
||||||
|
Option(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar")) match {
|
||||||
|
case Some(is) => Cache.readFullySync(is)
|
||||||
|
case None =>
|
||||||
|
Console.err.println(s"Error: bootstrap JAR not found")
|
||||||
|
sys.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val output0 = new File(options.output)
|
||||||
|
if (!options.force && output0.exists()) {
|
||||||
|
Console.err.println(s"Error: ${options.output} already exists, use -f option to force erasing it.")
|
||||||
|
sys.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
def zipEntries(zipStream: ZipInputStream): Iterator[(ZipEntry, Array[Byte])] =
|
||||||
|
new Iterator[(ZipEntry, Array[Byte])] {
|
||||||
|
var nextEntry = Option.empty[ZipEntry]
|
||||||
|
def update() =
|
||||||
|
nextEntry = Option(zipStream.getNextEntry)
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
def hasNext = nextEntry.nonEmpty
|
||||||
|
def next() = {
|
||||||
|
val ent = nextEntry.get
|
||||||
|
val data = Platform.readFullySync(zipStream)
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
(ent, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val helper = new Helper(options.common, remainingArgs)
|
||||||
|
|
||||||
|
val (_, isolatedArtifactFiles) =
|
||||||
|
options.isolated.targets.foldLeft((Vector.empty[String], Map.empty[String, (Seq[String], Seq[File])])) {
|
||||||
|
case ((done, acc), target) =>
|
||||||
|
val subRes = helper.res.subset(options.isolated.isolatedDeps.getOrElse(target, Nil).toSet)
|
||||||
|
val subArtifacts = subRes.artifacts.map(_.url)
|
||||||
|
|
||||||
|
val filteredSubArtifacts = subArtifacts.diff(done)
|
||||||
|
|
||||||
|
def subFiles0 = helper.fetch(
|
||||||
|
sources = false,
|
||||||
|
javadoc = false,
|
||||||
|
subset = options.isolated.isolatedDeps.getOrElse(target, Seq.empty).toSet
|
||||||
|
)
|
||||||
|
|
||||||
|
val (subUrls, subFiles) =
|
||||||
|
if (options.standalone)
|
||||||
|
(Nil, subFiles0)
|
||||||
|
else
|
||||||
|
(filteredSubArtifacts, Nil)
|
||||||
|
|
||||||
|
val updatedAcc = acc + (target -> (subUrls, subFiles))
|
||||||
|
|
||||||
|
(done ++ filteredSubArtifacts, updatedAcc)
|
||||||
|
}
|
||||||
|
|
||||||
|
val (urls, files) =
|
||||||
|
if (options.standalone)
|
||||||
|
(
|
||||||
|
Seq.empty[String],
|
||||||
|
helper.fetch(sources = false, javadoc = false)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
(
|
||||||
|
helper.artifacts(sources = false, javadoc = false).map(_.url),
|
||||||
|
Seq.empty[File]
|
||||||
|
)
|
||||||
|
|
||||||
|
val isolatedUrls = isolatedArtifactFiles.map { case (k, (v, _)) => k -> v }
|
||||||
|
val isolatedFiles = isolatedArtifactFiles.map { case (k, (_, v)) => k -> v }
|
||||||
|
|
||||||
|
val nonHttpUrls = urls.filter(s => !s.startsWith("http://") && !s.startsWith("https://"))
|
||||||
|
if (nonHttpUrls.nonEmpty)
|
||||||
|
Console.err.println(s"Warning: non HTTP URLs:\n${nonHttpUrls.mkString("\n")}")
|
||||||
|
|
||||||
|
val buffer = new ByteArrayOutputStream()
|
||||||
|
|
||||||
|
val bootstrapZip = new ZipInputStream(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar"))
|
||||||
|
val outputZip = new ZipOutputStream(buffer)
|
||||||
|
|
||||||
|
for ((ent, data) <- zipEntries(bootstrapZip)) {
|
||||||
|
outputZip.putNextEntry(ent)
|
||||||
|
outputZip.write(data)
|
||||||
|
outputZip.closeEntry()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val time = System.currentTimeMillis()
|
||||||
|
|
||||||
|
def putStringEntry(name: String, content: String): Unit = {
|
||||||
|
val entry = new ZipEntry(name)
|
||||||
|
entry.setTime(time)
|
||||||
|
|
||||||
|
outputZip.putNextEntry(entry)
|
||||||
|
outputZip.write(content.getBytes("UTF-8"))
|
||||||
|
outputZip.closeEntry()
|
||||||
|
}
|
||||||
|
|
||||||
|
def putEntryFromFile(name: String, f: File): Unit = {
|
||||||
|
val entry = new ZipEntry(name)
|
||||||
|
entry.setTime(f.lastModified())
|
||||||
|
|
||||||
|
outputZip.putNextEntry(entry)
|
||||||
|
outputZip.write(Cache.readFullySync(new FileInputStream(f)))
|
||||||
|
outputZip.closeEntry()
|
||||||
|
}
|
||||||
|
|
||||||
|
putStringEntry("bootstrap-jar-urls", urls.mkString("\n"))
|
||||||
|
|
||||||
|
if (options.isolated.anyIsolatedDep) {
|
||||||
|
putStringEntry("bootstrap-isolation-ids", options.isolated.targets.mkString("\n"))
|
||||||
|
|
||||||
|
for (target <- options.isolated.targets) {
|
||||||
|
val urls = isolatedUrls.getOrElse(target, Nil)
|
||||||
|
val files = isolatedFiles.getOrElse(target, Nil)
|
||||||
|
putStringEntry(s"bootstrap-isolation-$target-jar-urls", urls.mkString("\n"))
|
||||||
|
putStringEntry(s"bootstrap-isolation-$target-jar-resources", files.map(pathFor).mkString("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def pathFor(f: File) = s"jars/${f.getName}"
|
||||||
|
|
||||||
|
for (f <- files)
|
||||||
|
putEntryFromFile(pathFor(f), f)
|
||||||
|
|
||||||
|
putStringEntry("bootstrap-jar-resources", files.map(pathFor).mkString("\n"))
|
||||||
|
|
||||||
|
val propsEntry = new ZipEntry("bootstrap.properties")
|
||||||
|
propsEntry.setTime(time)
|
||||||
|
|
||||||
|
val properties = new Properties()
|
||||||
|
properties.setProperty("bootstrap.mainClass", options.mainClass)
|
||||||
|
if (!options.standalone)
|
||||||
|
properties.setProperty("bootstrap.jarDir", options.downloadDir)
|
||||||
|
|
||||||
|
outputZip.putNextEntry(propsEntry)
|
||||||
|
properties.store(outputZip, "")
|
||||||
|
outputZip.closeEntry()
|
||||||
|
|
||||||
|
outputZip.close()
|
||||||
|
|
||||||
|
// escaping of javaOpt possibly a bit loose :-|
|
||||||
|
val shellPreamble = Seq(
|
||||||
|
"#!/usr/bin/env sh",
|
||||||
|
"exec java -jar " + options.javaOpt.map(s => "'" + s.replace("'", "\\'") + "'").mkString(" ") + " \"$0\" \"$@\""
|
||||||
|
).mkString("", "\n", "\n")
|
||||||
|
|
||||||
|
try NIOFiles.write(output0.toPath, shellPreamble.getBytes("UTF-8") ++ buffer.toByteArray)
|
||||||
|
catch { case e: IOException =>
|
||||||
|
Console.err.println(s"Error while writing $output0: ${e.getMessage}")
|
||||||
|
sys.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
val perms = NIOFiles.getPosixFilePermissions(output0.toPath).asScala.toSet
|
||||||
|
|
||||||
|
var newPerms = perms
|
||||||
|
if (perms(PosixFilePermission.OWNER_READ))
|
||||||
|
newPerms += PosixFilePermission.OWNER_EXECUTE
|
||||||
|
if (perms(PosixFilePermission.GROUP_READ))
|
||||||
|
newPerms += PosixFilePermission.GROUP_EXECUTE
|
||||||
|
if (perms(PosixFilePermission.OTHERS_READ))
|
||||||
|
newPerms += PosixFilePermission.OTHERS_EXECUTE
|
||||||
|
|
||||||
|
if (newPerms != perms)
|
||||||
|
NIOFiles.setPosixFilePermissions(
|
||||||
|
output0.toPath,
|
||||||
|
newPerms.asJava
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
case e: UnsupportedOperationException =>
|
||||||
|
// Ignored
|
||||||
|
case e: IOException =>
|
||||||
|
Console.err.println(s"Error while making $output0 executable: ${e.getMessage}")
|
||||||
|
sys.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,603 +1,7 @@
|
||||||
package coursier
|
package coursier
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import java.io.{ FileInputStream, ByteArrayOutputStream, File, IOException }
|
import caseapp._
|
||||||
import java.net.URLClassLoader
|
|
||||||
import java.nio.file.{ Files => NIOFiles }
|
|
||||||
import java.nio.file.attribute.PosixFilePermission
|
|
||||||
import java.util.Properties
|
|
||||||
import java.util.zip.{ ZipEntry, ZipOutputStream, ZipInputStream }
|
|
||||||
|
|
||||||
import caseapp.{ HelpMessage => Help, ValueDescription => Value, ExtraName => Short, _ }
|
|
||||||
import coursier.util.Parse
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
|
||||||
import scala.language.reflectiveCalls
|
|
||||||
import scala.util.Try
|
|
||||||
|
|
||||||
case class CommonOptions(
|
|
||||||
@Help("Keep optional dependencies (Maven)")
|
|
||||||
keepOptional: Boolean,
|
|
||||||
@Help("Download mode (default: missing, that is fetch things missing from cache)")
|
|
||||||
@Value("offline|update-changing|update|missing|force")
|
|
||||||
@Short("m")
|
|
||||||
mode: String = "default",
|
|
||||||
@Help("Quiet output")
|
|
||||||
@Short("q")
|
|
||||||
quiet: Boolean,
|
|
||||||
@Help("Increase verbosity (specify several times to increase more)")
|
|
||||||
@Short("v")
|
|
||||||
verbose: List[Unit],
|
|
||||||
@Help("Maximum number of resolution iterations (specify a negative value for unlimited, default: 100)")
|
|
||||||
@Short("N")
|
|
||||||
maxIterations: Int = 100,
|
|
||||||
@Help("Repositories - for multiple repositories, separate with comma and/or repeat this option (e.g. -r central,ivy2local -r sonatype-snapshots, or equivalently -r central,ivy2local,sonatype-snapshots)")
|
|
||||||
@Short("r")
|
|
||||||
repository: List[String],
|
|
||||||
@Help("Do not add default repositories (~/.ivy2/local, and Central)")
|
|
||||||
noDefault: Boolean = false,
|
|
||||||
@Help("Modify names in Maven repository paths for SBT plugins")
|
|
||||||
sbtPluginHack: Boolean = false,
|
|
||||||
@Help("Drop module attributes starting with 'info.' - these are sometimes used by projects built with SBT")
|
|
||||||
dropInfoAttr: Boolean = false,
|
|
||||||
@Help("Force module version")
|
|
||||||
@Value("organization:name:forcedVersion")
|
|
||||||
@Short("V")
|
|
||||||
forceVersion: List[String],
|
|
||||||
@Help("Exclude module")
|
|
||||||
@Value("organization:name")
|
|
||||||
@Short("E")
|
|
||||||
exclude: List[String],
|
|
||||||
@Help("Consider provided dependencies to be intransitive. Applies to all the provided dependencies.")
|
|
||||||
intransitive: Boolean,
|
|
||||||
@Help("Classifiers that should be fetched")
|
|
||||||
@Value("classifier1,classifier2,...")
|
|
||||||
@Short("C")
|
|
||||||
classifier: List[String],
|
|
||||||
@Help("Default configuration (default(compile) by default)")
|
|
||||||
@Value("configuration")
|
|
||||||
@Short("c")
|
|
||||||
defaultConfiguration: String = "default(compile)",
|
|
||||||
@Help("Maximum number of parallel downloads (default: 6)")
|
|
||||||
@Short("n")
|
|
||||||
parallel: Int = 6,
|
|
||||||
@Help("Checksums")
|
|
||||||
@Value("checksum1,checksum2,... - end with none to allow for no checksum validation if none are available")
|
|
||||||
checksum: List[String],
|
|
||||||
@Recurse
|
|
||||||
cacheOptions: CacheOptions
|
|
||||||
) {
|
|
||||||
val verbose0 = verbose.length - (if (quiet) 1 else 0)
|
|
||||||
lazy val classifier0 = classifier.flatMap(_.split(',')).filter(_.nonEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
case class CacheOptions(
|
|
||||||
@Help("Cache directory (defaults to environment variable COURSIER_CACHE or ~/.coursier/cache/v1)")
|
|
||||||
@Short("C")
|
|
||||||
cache: String = Cache.default.toString
|
|
||||||
)
|
|
||||||
|
|
||||||
sealed abstract class CoursierCommand extends Command
|
|
||||||
|
|
||||||
case class Resolve(
|
|
||||||
@Recurse
|
|
||||||
common: CommonOptions
|
|
||||||
) extends CoursierCommand {
|
|
||||||
|
|
||||||
// the `val helper = ` part is needed because of DelayedInit it seems
|
|
||||||
val helper = new Helper(common, remainingArgs, printResultStdout = true)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Fetch(
|
|
||||||
@Help("Fetch source artifacts")
|
|
||||||
@Short("S")
|
|
||||||
sources: Boolean,
|
|
||||||
@Help("Fetch javadoc artifacts")
|
|
||||||
@Short("D")
|
|
||||||
javadoc: Boolean,
|
|
||||||
@Help("Print java -cp compatible output")
|
|
||||||
@Short("p")
|
|
||||||
classpath: Boolean,
|
|
||||||
@Help("Fetch artifacts even if the resolution is errored")
|
|
||||||
force: Boolean,
|
|
||||||
@Recurse
|
|
||||||
common: CommonOptions
|
|
||||||
) extends CoursierCommand {
|
|
||||||
|
|
||||||
val helper = new Helper(common, remainingArgs, ignoreErrors = force)
|
|
||||||
|
|
||||||
val files0 = helper.fetch(sources = sources, javadoc = javadoc)
|
|
||||||
|
|
||||||
val out =
|
|
||||||
if (classpath)
|
|
||||||
files0
|
|
||||||
.map(_.toString)
|
|
||||||
.mkString(File.pathSeparator)
|
|
||||||
else
|
|
||||||
files0
|
|
||||||
.map(_.toString)
|
|
||||||
.mkString("\n")
|
|
||||||
|
|
||||||
println(out)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
case class IsolatedLoaderOptions(
|
|
||||||
@Value("target:dependency")
|
|
||||||
@Short("I")
|
|
||||||
isolated: List[String],
|
|
||||||
@Help("Comma-separated isolation targets")
|
|
||||||
@Short("i")
|
|
||||||
isolateTarget: List[String]
|
|
||||||
) {
|
|
||||||
|
|
||||||
def anyIsolatedDep = isolateTarget.nonEmpty || isolated.nonEmpty
|
|
||||||
|
|
||||||
lazy val targets = {
|
|
||||||
val l = isolateTarget.flatMap(_.split(',')).filter(_.nonEmpty)
|
|
||||||
val (invalid, valid) = l.partition(_.contains(":"))
|
|
||||||
if (invalid.nonEmpty) {
|
|
||||||
Console.err.println(s"Invalid target IDs:")
|
|
||||||
for (t <- invalid)
|
|
||||||
Console.err.println(s" $t")
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
if (valid.isEmpty)
|
|
||||||
Array("default")
|
|
||||||
else
|
|
||||||
valid.toArray
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy val (validIsolated, unrecognizedIsolated) = isolated.partition(s => targets.exists(t => s.startsWith(t + ":")))
|
|
||||||
|
|
||||||
def check() = {
|
|
||||||
if (unrecognizedIsolated.nonEmpty) {
|
|
||||||
Console.err.println(s"Unrecognized isolation targets in:")
|
|
||||||
for (i <- unrecognizedIsolated)
|
|
||||||
Console.err.println(s" $i")
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy val rawIsolated = validIsolated.map { s =>
|
|
||||||
val Array(target, dep) = s.split(":", 2)
|
|
||||||
target -> dep
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy val isolatedModuleVersions = rawIsolated.groupBy { case (t, _) => t }.map {
|
|
||||||
case (t, l) =>
|
|
||||||
val (errors, modVers) = Parse.moduleVersions(l.map { case (_, d) => d })
|
|
||||||
|
|
||||||
if (errors.nonEmpty) {
|
|
||||||
errors.foreach(Console.err.println)
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
|
|
||||||
t -> modVers
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy val isolatedDeps = isolatedModuleVersions.map {
|
|
||||||
case (t, l) =>
|
|
||||||
t -> l.map {
|
|
||||||
case (mod, ver) =>
|
|
||||||
Dependency(mod, ver, configuration = "runtime")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object Launch {
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
def mainClassLoader(cl: ClassLoader): Option[ClassLoader] =
|
|
||||||
if (cl == null)
|
|
||||||
None
|
|
||||||
else {
|
|
||||||
val isMainLoader = try {
|
|
||||||
val cl0 = cl.asInstanceOf[Object {
|
|
||||||
def isBootstrapLoader: Boolean
|
|
||||||
}]
|
|
||||||
|
|
||||||
cl0.isBootstrapLoader
|
|
||||||
} catch {
|
|
||||||
case e: Exception =>
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMainLoader)
|
|
||||||
Some(cl)
|
|
||||||
else
|
|
||||||
mainClassLoader(cl.getParent)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Launch(
|
|
||||||
@Short("M")
|
|
||||||
@Short("main")
|
|
||||||
mainClass: String,
|
|
||||||
@Recurse
|
|
||||||
isolated: IsolatedLoaderOptions,
|
|
||||||
@Recurse
|
|
||||||
common: CommonOptions
|
|
||||||
) extends CoursierCommand {
|
|
||||||
|
|
||||||
val (rawDependencies, extraArgs) = {
|
|
||||||
val idxOpt = Some(remainingArgs.indexOf("--")).filter(_ >= 0)
|
|
||||||
idxOpt.fold((remainingArgs, Seq.empty[String])) { idx =>
|
|
||||||
val (l, r) = remainingArgs.splitAt(idx)
|
|
||||||
assert(r.nonEmpty)
|
|
||||||
(l, r.tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val helper = new Helper(
|
|
||||||
common,
|
|
||||||
rawDependencies ++ isolated.rawIsolated.map { case (_, dep) => dep }
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
val files0 = helper.fetch(sources = false, javadoc = false)
|
|
||||||
|
|
||||||
val contextLoader = Thread.currentThread().getContextClassLoader
|
|
||||||
|
|
||||||
val parentLoader0: ClassLoader =
|
|
||||||
if (Try(contextLoader.loadClass("coursier.cli.Launch")).isSuccess)
|
|
||||||
Launch.mainClassLoader(contextLoader)
|
|
||||||
.flatMap(cl => Option(cl.getParent))
|
|
||||||
.getOrElse {
|
|
||||||
if (common.verbose0 >= 0)
|
|
||||||
Console.err.println(
|
|
||||||
"Warning: cannot find the main ClassLoader that launched coursier. " +
|
|
||||||
"Was coursier launched by its main launcher? " +
|
|
||||||
"The ClassLoader of the application that is about to be launched will be intertwined " +
|
|
||||||
"with the one of coursier, which may be a problem if their dependencies conflict."
|
|
||||||
)
|
|
||||||
contextLoader
|
|
||||||
}
|
|
||||||
else
|
|
||||||
// proguarded -> no risk of conflicts, no need to find a specific ClassLoader
|
|
||||||
contextLoader
|
|
||||||
|
|
||||||
val (parentLoader, filteredFiles) =
|
|
||||||
if (isolated.isolated.isEmpty)
|
|
||||||
(parentLoader0, files0)
|
|
||||||
else {
|
|
||||||
val (isolatedLoader, filteredFiles0) = isolated.targets.foldLeft((parentLoader0, files0)) {
|
|
||||||
case ((parent, files0), target) =>
|
|
||||||
|
|
||||||
// FIXME These were already fetched above
|
|
||||||
val isolatedFiles = helper.fetch(
|
|
||||||
sources = false,
|
|
||||||
javadoc = false,
|
|
||||||
subset = isolated.isolatedDeps.getOrElse(target, Seq.empty).toSet
|
|
||||||
)
|
|
||||||
|
|
||||||
if (common.verbose0 >= 1) {
|
|
||||||
Console.err.println(s"Isolated loader files:")
|
|
||||||
for (f <- isolatedFiles.map(_.toString).sorted)
|
|
||||||
Console.err.println(s" $f")
|
|
||||||
}
|
|
||||||
|
|
||||||
val isolatedLoader = new IsolatedClassLoader(
|
|
||||||
isolatedFiles.map(_.toURI.toURL).toArray,
|
|
||||||
parent,
|
|
||||||
Array(target)
|
|
||||||
)
|
|
||||||
|
|
||||||
val filteredFiles0 = files0.filterNot(isolatedFiles.toSet)
|
|
||||||
|
|
||||||
(isolatedLoader, filteredFiles0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (common.verbose0 >= 1) {
|
|
||||||
Console.err.println(s"Remaining files:")
|
|
||||||
for (f <- filteredFiles0.map(_.toString).sorted)
|
|
||||||
Console.err.println(s" $f")
|
|
||||||
}
|
|
||||||
|
|
||||||
(isolatedLoader, filteredFiles0)
|
|
||||||
}
|
|
||||||
|
|
||||||
val loader = new URLClassLoader(
|
|
||||||
filteredFiles.map(_.toURI.toURL).toArray,
|
|
||||||
parentLoader
|
|
||||||
)
|
|
||||||
|
|
||||||
val mainClass0 =
|
|
||||||
if (mainClass.nonEmpty) mainClass
|
|
||||||
else {
|
|
||||||
val mainClasses = Helper.mainClasses(loader)
|
|
||||||
|
|
||||||
val mainClass =
|
|
||||||
if (mainClasses.isEmpty) {
|
|
||||||
Helper.errPrintln("No main class found. Specify one with -M or --main.")
|
|
||||||
sys.exit(255)
|
|
||||||
} else if (mainClasses.size == 1) {
|
|
||||||
val (_, mainClass) = mainClasses.head
|
|
||||||
mainClass
|
|
||||||
} else {
|
|
||||||
// Trying to get the main class of the first artifact
|
|
||||||
val mainClassOpt = for {
|
|
||||||
(module, _, _) <- helper.moduleVersionConfigs.headOption
|
|
||||||
mainClass <- mainClasses.collectFirst {
|
|
||||||
case ((org, name), mainClass)
|
|
||||||
if org == module.organization && (
|
|
||||||
module.name == name ||
|
|
||||||
module.name.startsWith(name + "_") // Ignore cross version suffix
|
|
||||||
) =>
|
|
||||||
mainClass
|
|
||||||
}
|
|
||||||
} yield mainClass
|
|
||||||
|
|
||||||
mainClassOpt.getOrElse {
|
|
||||||
Helper.errPrintln(s"Cannot find default main class. Specify one with -M or --main.")
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mainClass
|
|
||||||
}
|
|
||||||
|
|
||||||
val cls =
|
|
||||||
try loader.loadClass(mainClass0)
|
|
||||||
catch { case e: ClassNotFoundException =>
|
|
||||||
Helper.errPrintln(s"Error: class $mainClass0 not found")
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
val method =
|
|
||||||
try cls.getMethod("main", classOf[Array[String]])
|
|
||||||
catch { case e: NoSuchMethodException =>
|
|
||||||
Helper.errPrintln(s"Error: method main not found in $mainClass0")
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (common.verbose0 >= 1)
|
|
||||||
Helper.errPrintln(s"Launching $mainClass0 ${extraArgs.mkString(" ")}")
|
|
||||||
else if (common.verbose0 == 0)
|
|
||||||
Helper.errPrintln(s"Launching")
|
|
||||||
|
|
||||||
Thread.currentThread().setContextClassLoader(loader)
|
|
||||||
method.invoke(null, extraArgs.toArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Bootstrap(
|
|
||||||
@Short("M")
|
|
||||||
@Short("main")
|
|
||||||
mainClass: String,
|
|
||||||
@Short("o")
|
|
||||||
output: String = "bootstrap",
|
|
||||||
@Short("D")
|
|
||||||
downloadDir: String,
|
|
||||||
@Short("f")
|
|
||||||
force: Boolean,
|
|
||||||
@Help("Generate a standalone launcher, with all JARs included, instead of one downloading its dependencies on startup.")
|
|
||||||
@Short("s")
|
|
||||||
standalone: Boolean,
|
|
||||||
@Help("Set Java properties in the generated launcher.")
|
|
||||||
@Value("key=value")
|
|
||||||
@Short("P")
|
|
||||||
property: List[String],
|
|
||||||
@Help("Set Java command-line options in the generated launcher.")
|
|
||||||
@Value("option")
|
|
||||||
@Short("J")
|
|
||||||
javaOpt: List[String],
|
|
||||||
@Recurse
|
|
||||||
isolated: IsolatedLoaderOptions,
|
|
||||||
@Recurse
|
|
||||||
common: CommonOptions
|
|
||||||
) extends CoursierCommand {
|
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
|
||||||
|
|
||||||
if (mainClass.isEmpty) {
|
|
||||||
Console.err.println(s"Error: no main class specified. Specify one with -M or --main")
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!standalone && downloadDir.isEmpty) {
|
|
||||||
Console.err.println(s"Error: no download dir specified. Specify one with -D or --download-dir")
|
|
||||||
Console.err.println("E.g. -D \"\\$HOME/.app-name/jars\"")
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
|
|
||||||
val (validProperties, wrongProperties) = property.partition(_.contains("="))
|
|
||||||
if (wrongProperties.nonEmpty) {
|
|
||||||
Console.err.println(s"Wrong -P / --property option(s):\n${wrongProperties.mkString("\n")}")
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
|
|
||||||
val properties0 = validProperties.map { s =>
|
|
||||||
val idx = s.indexOf('=')
|
|
||||||
assert(idx >= 0)
|
|
||||||
(s.take(idx), s.drop(idx + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
val bootstrapJar =
|
|
||||||
Option(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar")) match {
|
|
||||||
case Some(is) => Cache.readFullySync(is)
|
|
||||||
case None =>
|
|
||||||
Console.err.println(s"Error: bootstrap JAR not found")
|
|
||||||
sys.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
val output0 = new File(output)
|
|
||||||
if (!force && output0.exists()) {
|
|
||||||
Console.err.println(s"Error: $output already exists, use -f option to force erasing it.")
|
|
||||||
sys.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zipEntries(zipStream: ZipInputStream): Iterator[(ZipEntry, Array[Byte])] =
|
|
||||||
new Iterator[(ZipEntry, Array[Byte])] {
|
|
||||||
var nextEntry = Option.empty[ZipEntry]
|
|
||||||
def update() =
|
|
||||||
nextEntry = Option(zipStream.getNextEntry)
|
|
||||||
|
|
||||||
update()
|
|
||||||
|
|
||||||
def hasNext = nextEntry.nonEmpty
|
|
||||||
def next() = {
|
|
||||||
val ent = nextEntry.get
|
|
||||||
val data = Platform.readFullySync(zipStream)
|
|
||||||
|
|
||||||
update()
|
|
||||||
|
|
||||||
(ent, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val helper = new Helper(common, remainingArgs)
|
|
||||||
|
|
||||||
val (_, isolatedArtifactFiles) =
|
|
||||||
isolated.targets.foldLeft((Vector.empty[String], Map.empty[String, (Seq[String], Seq[File])])) {
|
|
||||||
case ((done, acc), target) =>
|
|
||||||
val subRes = helper.res.subset(isolated.isolatedDeps.getOrElse(target, Nil).toSet)
|
|
||||||
val subArtifacts = subRes.artifacts.map(_.url)
|
|
||||||
|
|
||||||
val filteredSubArtifacts = subArtifacts.diff(done)
|
|
||||||
|
|
||||||
def subFiles0 = helper.fetch(
|
|
||||||
sources = false,
|
|
||||||
javadoc = false,
|
|
||||||
subset = isolated.isolatedDeps.getOrElse(target, Seq.empty).toSet
|
|
||||||
)
|
|
||||||
|
|
||||||
val (subUrls, subFiles) =
|
|
||||||
if (standalone)
|
|
||||||
(Nil, subFiles0)
|
|
||||||
else
|
|
||||||
(filteredSubArtifacts, Nil)
|
|
||||||
|
|
||||||
val updatedAcc = acc + (target -> (subUrls, subFiles))
|
|
||||||
|
|
||||||
(done ++ filteredSubArtifacts, updatedAcc)
|
|
||||||
}
|
|
||||||
|
|
||||||
val (urls, files) =
|
|
||||||
if (standalone)
|
|
||||||
(
|
|
||||||
Seq.empty[String],
|
|
||||||
helper.fetch(sources = false, javadoc = false)
|
|
||||||
)
|
|
||||||
else
|
|
||||||
(
|
|
||||||
helper.artifacts(sources = false, javadoc = false).map(_.url),
|
|
||||||
Seq.empty[File]
|
|
||||||
)
|
|
||||||
|
|
||||||
val isolatedUrls = isolatedArtifactFiles.map { case (k, (v, _)) => k -> v }
|
|
||||||
val isolatedFiles = isolatedArtifactFiles.map { case (k, (_, v)) => k -> v }
|
|
||||||
|
|
||||||
val nonHttpUrls = urls.filter(s => !s.startsWith("http://") && !s.startsWith("https://"))
|
|
||||||
if (nonHttpUrls.nonEmpty)
|
|
||||||
Console.err.println(s"Warning: non HTTP URLs:\n${nonHttpUrls.mkString("\n")}")
|
|
||||||
|
|
||||||
val buffer = new ByteArrayOutputStream()
|
|
||||||
|
|
||||||
val bootstrapZip = new ZipInputStream(Thread.currentThread().getContextClassLoader.getResourceAsStream("bootstrap.jar"))
|
|
||||||
val outputZip = new ZipOutputStream(buffer)
|
|
||||||
|
|
||||||
for ((ent, data) <- zipEntries(bootstrapZip)) {
|
|
||||||
outputZip.putNextEntry(ent)
|
|
||||||
outputZip.write(data)
|
|
||||||
outputZip.closeEntry()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val time = System.currentTimeMillis()
|
|
||||||
|
|
||||||
def putStringEntry(name: String, content: String): Unit = {
|
|
||||||
val entry = new ZipEntry(name)
|
|
||||||
entry.setTime(time)
|
|
||||||
|
|
||||||
outputZip.putNextEntry(entry)
|
|
||||||
outputZip.write(content.getBytes("UTF-8"))
|
|
||||||
outputZip.closeEntry()
|
|
||||||
}
|
|
||||||
|
|
||||||
def putEntryFromFile(name: String, f: File): Unit = {
|
|
||||||
val entry = new ZipEntry(name)
|
|
||||||
entry.setTime(f.lastModified())
|
|
||||||
|
|
||||||
outputZip.putNextEntry(entry)
|
|
||||||
outputZip.write(Cache.readFullySync(new FileInputStream(f)))
|
|
||||||
outputZip.closeEntry()
|
|
||||||
}
|
|
||||||
|
|
||||||
putStringEntry("bootstrap-jar-urls", urls.mkString("\n"))
|
|
||||||
|
|
||||||
if (isolated.anyIsolatedDep) {
|
|
||||||
putStringEntry("bootstrap-isolation-ids", isolated.targets.mkString("\n"))
|
|
||||||
|
|
||||||
for (target <- isolated.targets) {
|
|
||||||
val urls = isolatedUrls.getOrElse(target, Nil)
|
|
||||||
val files = isolatedFiles.getOrElse(target, Nil)
|
|
||||||
putStringEntry(s"bootstrap-isolation-$target-jar-urls", urls.mkString("\n"))
|
|
||||||
putStringEntry(s"bootstrap-isolation-$target-jar-resources", files.map(pathFor).mkString("\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def pathFor(f: File) = s"jars/${f.getName}"
|
|
||||||
|
|
||||||
for (f <- files)
|
|
||||||
putEntryFromFile(pathFor(f), f)
|
|
||||||
|
|
||||||
putStringEntry("bootstrap-jar-resources", files.map(pathFor).mkString("\n"))
|
|
||||||
|
|
||||||
val propsEntry = new ZipEntry("bootstrap.properties")
|
|
||||||
propsEntry.setTime(time)
|
|
||||||
|
|
||||||
val properties = new Properties()
|
|
||||||
properties.setProperty("bootstrap.mainClass", mainClass)
|
|
||||||
if (!standalone)
|
|
||||||
properties.setProperty("bootstrap.jarDir", downloadDir)
|
|
||||||
|
|
||||||
outputZip.putNextEntry(propsEntry)
|
|
||||||
properties.store(outputZip, "")
|
|
||||||
outputZip.closeEntry()
|
|
||||||
|
|
||||||
outputZip.close()
|
|
||||||
|
|
||||||
// escaping of javaOpt possibly a bit loose :-|
|
|
||||||
val shellPreamble = Seq(
|
|
||||||
"#!/usr/bin/env sh",
|
|
||||||
"exec java -jar " + javaOpt.map(s => "'" + s.replace("'", "\\'") + "'").mkString(" ") + " \"$0\" \"$@\""
|
|
||||||
).mkString("", "\n", "\n")
|
|
||||||
|
|
||||||
try NIOFiles.write(output0.toPath, shellPreamble.getBytes("UTF-8") ++ buffer.toByteArray)
|
|
||||||
catch { case e: IOException =>
|
|
||||||
Console.err.println(s"Error while writing $output0: ${e.getMessage}")
|
|
||||||
sys.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
val perms = NIOFiles.getPosixFilePermissions(output0.toPath).asScala.toSet
|
|
||||||
|
|
||||||
var newPerms = perms
|
|
||||||
if (perms(PosixFilePermission.OWNER_READ))
|
|
||||||
newPerms += PosixFilePermission.OWNER_EXECUTE
|
|
||||||
if (perms(PosixFilePermission.GROUP_READ))
|
|
||||||
newPerms += PosixFilePermission.GROUP_EXECUTE
|
|
||||||
if (perms(PosixFilePermission.OTHERS_READ))
|
|
||||||
newPerms += PosixFilePermission.OTHERS_EXECUTE
|
|
||||||
|
|
||||||
if (newPerms != perms)
|
|
||||||
NIOFiles.setPosixFilePermissions(
|
|
||||||
output0.toPath,
|
|
||||||
newPerms.asJava
|
|
||||||
)
|
|
||||||
} catch {
|
|
||||||
case e: UnsupportedOperationException =>
|
|
||||||
// Ignored
|
|
||||||
case e: IOException =>
|
|
||||||
Console.err.println(s"Error while making $output0 executable: ${e.getMessage}")
|
|
||||||
sys.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object Coursier extends CommandAppOf[CoursierCommand] {
|
object Coursier extends CommandAppOf[CoursierCommand] {
|
||||||
override def appName = "Coursier"
|
override def appName = "Coursier"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
package coursier
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import caseapp.{ HelpMessage => Help, ValueDescription => Value, ExtraName => Short, _ }
|
||||||
|
|
||||||
|
import coursier.util.Parse
|
||||||
|
|
||||||
|
case class CommonOptions(
|
||||||
|
@Help("Keep optional dependencies (Maven)")
|
||||||
|
keepOptional: Boolean,
|
||||||
|
@Help("Download mode (default: missing, that is fetch things missing from cache)")
|
||||||
|
@Value("offline|update-changing|update|missing|force")
|
||||||
|
@Short("m")
|
||||||
|
mode: String = "default",
|
||||||
|
@Help("Quiet output")
|
||||||
|
@Short("q")
|
||||||
|
quiet: Boolean,
|
||||||
|
@Help("Increase verbosity (specify several times to increase more)")
|
||||||
|
@Short("v")
|
||||||
|
verbose: List[Unit],
|
||||||
|
@Help("Maximum number of resolution iterations (specify a negative value for unlimited, default: 100)")
|
||||||
|
@Short("N")
|
||||||
|
maxIterations: Int = 100,
|
||||||
|
@Help("Repositories - for multiple repositories, separate with comma and/or repeat this option (e.g. -r central,ivy2local -r sonatype-snapshots, or equivalently -r central,ivy2local,sonatype-snapshots)")
|
||||||
|
@Short("r")
|
||||||
|
repository: List[String],
|
||||||
|
@Help("Do not add default repositories (~/.ivy2/local, and Central)")
|
||||||
|
noDefault: Boolean = false,
|
||||||
|
@Help("Modify names in Maven repository paths for SBT plugins")
|
||||||
|
sbtPluginHack: Boolean = false,
|
||||||
|
@Help("Drop module attributes starting with 'info.' - these are sometimes used by projects built with SBT")
|
||||||
|
dropInfoAttr: Boolean = false,
|
||||||
|
@Help("Force module version")
|
||||||
|
@Value("organization:name:forcedVersion")
|
||||||
|
@Short("V")
|
||||||
|
forceVersion: List[String],
|
||||||
|
@Help("Exclude module")
|
||||||
|
@Value("organization:name")
|
||||||
|
@Short("E")
|
||||||
|
exclude: List[String],
|
||||||
|
@Help("Consider provided dependencies to be intransitive. Applies to all the provided dependencies.")
|
||||||
|
intransitive: Boolean,
|
||||||
|
@Help("Classifiers that should be fetched")
|
||||||
|
@Value("classifier1,classifier2,...")
|
||||||
|
@Short("C")
|
||||||
|
classifier: List[String],
|
||||||
|
@Help("Default configuration (default(compile) by default)")
|
||||||
|
@Value("configuration")
|
||||||
|
@Short("c")
|
||||||
|
defaultConfiguration: String = "default(compile)",
|
||||||
|
@Help("Maximum number of parallel downloads (default: 6)")
|
||||||
|
@Short("n")
|
||||||
|
parallel: Int = 6,
|
||||||
|
@Help("Checksums")
|
||||||
|
@Value("checksum1,checksum2,... - end with none to allow for no checksum validation if none are available")
|
||||||
|
checksum: List[String],
|
||||||
|
@Recurse
|
||||||
|
cacheOptions: CacheOptions
|
||||||
|
) {
|
||||||
|
val verbose0 = verbose.length - (if (quiet) 1 else 0)
|
||||||
|
lazy val classifier0 = classifier.flatMap(_.split(',')).filter(_.nonEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class CacheOptions(
|
||||||
|
@Help("Cache directory (defaults to environment variable COURSIER_CACHE or ~/.coursier/cache/v1)")
|
||||||
|
@Short("C")
|
||||||
|
cache: String = Cache.default.toString
|
||||||
|
)
|
||||||
|
|
||||||
|
case class IsolatedLoaderOptions(
|
||||||
|
@Value("target:dependency")
|
||||||
|
@Short("I")
|
||||||
|
isolated: List[String],
|
||||||
|
@Help("Comma-separated isolation targets")
|
||||||
|
@Short("i")
|
||||||
|
isolateTarget: List[String]
|
||||||
|
) {
|
||||||
|
|
||||||
|
def anyIsolatedDep = isolateTarget.nonEmpty || isolated.nonEmpty
|
||||||
|
|
||||||
|
lazy val targets = {
|
||||||
|
val l = isolateTarget.flatMap(_.split(',')).filter(_.nonEmpty)
|
||||||
|
val (invalid, valid) = l.partition(_.contains(":"))
|
||||||
|
if (invalid.nonEmpty) {
|
||||||
|
Console.err.println(s"Invalid target IDs:")
|
||||||
|
for (t <- invalid)
|
||||||
|
Console.err.println(s" $t")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
if (valid.isEmpty)
|
||||||
|
Array("default")
|
||||||
|
else
|
||||||
|
valid.toArray
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val (validIsolated, unrecognizedIsolated) = isolated.partition(s => targets.exists(t => s.startsWith(t + ":")))
|
||||||
|
|
||||||
|
def check() = {
|
||||||
|
if (unrecognizedIsolated.nonEmpty) {
|
||||||
|
Console.err.println(s"Unrecognized isolation targets in:")
|
||||||
|
for (i <- unrecognizedIsolated)
|
||||||
|
Console.err.println(s" $i")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val rawIsolated = validIsolated.map { s =>
|
||||||
|
val Array(target, dep) = s.split(":", 2)
|
||||||
|
target -> dep
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val isolatedModuleVersions = rawIsolated.groupBy { case (t, _) => t }.map {
|
||||||
|
case (t, l) =>
|
||||||
|
val (errors, modVers) = Parse.moduleVersions(l.map { case (_, d) => d })
|
||||||
|
|
||||||
|
if (errors.nonEmpty) {
|
||||||
|
errors.foreach(Console.err.println)
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
t -> modVers
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val isolatedDeps = isolatedModuleVersions.map {
|
||||||
|
case (t, l) =>
|
||||||
|
t -> l.map {
|
||||||
|
case (mod, ver) =>
|
||||||
|
Dependency(mod, ver, configuration = "runtime")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case class FetchOptions(
|
||||||
|
@Help("Fetch source artifacts")
|
||||||
|
@Short("S")
|
||||||
|
sources: Boolean,
|
||||||
|
@Help("Fetch javadoc artifacts")
|
||||||
|
@Short("D")
|
||||||
|
javadoc: Boolean,
|
||||||
|
@Help("Print java -cp compatible output")
|
||||||
|
@Short("p")
|
||||||
|
classpath: Boolean,
|
||||||
|
@Help("Fetch artifacts even if the resolution is errored")
|
||||||
|
force: Boolean,
|
||||||
|
@Recurse
|
||||||
|
common: CommonOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
case class LaunchOptions(
|
||||||
|
@Short("M")
|
||||||
|
@Short("main")
|
||||||
|
mainClass: String,
|
||||||
|
@Recurse
|
||||||
|
isolated: IsolatedLoaderOptions,
|
||||||
|
@Recurse
|
||||||
|
common: CommonOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
case class BootstrapOptions(
|
||||||
|
@Short("M")
|
||||||
|
@Short("main")
|
||||||
|
mainClass: String,
|
||||||
|
@Short("o")
|
||||||
|
output: String = "bootstrap",
|
||||||
|
@Short("D")
|
||||||
|
downloadDir: String,
|
||||||
|
@Short("f")
|
||||||
|
force: Boolean,
|
||||||
|
@Help("Generate a standalone launcher, with all JARs included, instead of one downloading its dependencies on startup.")
|
||||||
|
@Short("s")
|
||||||
|
standalone: Boolean,
|
||||||
|
@Help("Set Java properties in the generated launcher.")
|
||||||
|
@Value("key=value")
|
||||||
|
@Short("P")
|
||||||
|
property: List[String],
|
||||||
|
@Help("Set Java command-line options in the generated launcher.")
|
||||||
|
@Value("option")
|
||||||
|
@Short("J")
|
||||||
|
javaOpt: List[String],
|
||||||
|
@Recurse
|
||||||
|
isolated: IsolatedLoaderOptions,
|
||||||
|
@Recurse
|
||||||
|
common: CommonOptions
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue