diff --git a/cli/src/main/scala/coursier/cli/Coursier.scala b/cli/src/main/scala/coursier/cli/Coursier.scala index 0f9a6e745..4bc3d55d5 100644 --- a/cli/src/main/scala/coursier/cli/Coursier.scala +++ b/cli/src/main/scala/coursier/cli/Coursier.scala @@ -97,6 +97,9 @@ case class Launch( @ExtraName("M") @ExtraName("main") mainClass: String, + @ExtraName("c") + @HelpMessage("Assume coursier is a dependency of the launched app, and share the coursier dependency of the launcher with it - allows the launched app to get the resolution that launched it via ResolutionClassLoader") + addCoursier: Boolean, @Recurse common: CommonOptions ) extends CoursierCommand { @@ -110,7 +113,29 @@ case class Launch( } } - val helper = new Helper(common, rawDependencies) + val extraForceVersions = + if (addCoursier) + ??? + else + Seq.empty[String] + + val dontFilterOut = + if (addCoursier) { + val url = classOf[coursier.core.Resolution].getProtectionDomain.getCodeSource.getLocation + + if (url.getProtocol == "file") + Seq(new File(url.getPath)) + else { + Console.err.println(s"Cannot get the location of the JAR of coursier ($url not a file URL)") + sys.exit(255) + } + } else + Seq.empty[File] + + val helper = new Helper( + common.copy(forceVersion = common.forceVersion ++ extraForceVersions), + rawDependencies + ) val files0 = helper.fetch(sources = false, javadoc = false) @@ -118,7 +143,7 @@ case class Launch( files0.map(_.toURI.toURL).toArray, new ClasspathFilter( Thread.currentThread().getContextClassLoader, - Coursier.baseCp.map(new File(_)).toSet, + Coursier.baseCp.map(new File(_)).toSet -- dontFilterOut, exclude = true ) ) diff --git a/core/jvm/src/main/scala/coursier/ResolutionClassLoader.scala b/core/jvm/src/main/scala/coursier/ResolutionClassLoader.scala new file mode 100644 index 000000000..3f852bb9c --- /dev/null +++ b/core/jvm/src/main/scala/coursier/ResolutionClassLoader.scala @@ -0,0 +1,37 @@ +package coursier + +import java.io.File +import java.net.URLClassLoader + +import coursier.util.ClasspathFilter + +class ResolutionClassLoader( + val resolution: Resolution, + val artifacts: Seq[(Dependency, Artifact, File)], + parent: ClassLoader +) extends URLClassLoader( + artifacts.map { case (_, _, f) => f.toURI.toURL }.toArray, + parent +) { + + /** + * Filtered version of this `ClassLoader`, exposing only `dependencies` and their + * their transitive dependencies, and filtering out the other dependencies from + * `resolution` - for `ClassLoader` isolation. + * + * An application launched by `coursier launch -C` has `ResolutionClassLoader` set as its + * context `ClassLoader` (can be obtain with `Thread.currentThread().getContextClassLoader`). + * If it aims at doing `ClassLoader` isolation, exposing only a dependency `dep` to the isolated + * things, `filter(dep)` provides a `ClassLoader` that loaded `dep` and all its transitive + * dependencies through the same loader as the contextual one, but that "exposes" only + * `dep` and its transitive dependencies, nothing more. + */ + def filter(dependencies: Set[Dependency]): ClassLoader = { + val subRes = resolution.subset(dependencies) + val subArtifacts = subRes.dependencyArtifacts.map { case (_, a) => a }.toSet + val subFiles = artifacts.collect { case (_, a, f) if subArtifacts(a) => f } + + new ClasspathFilter(this, subFiles.toSet, exclude = false) + } + +}