Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit e88e587

Browse files
committed
Add coursier to resolve and fetch dependencies for plugins
1 parent eaef935 commit e88e587

File tree

5 files changed

+126
-19
lines changed

5 files changed

+126
-19
lines changed

build.sbt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ libraryDependencies += "com.fazecast" % "jSerialComm" % "[2.0.0,3.0.0)"
8989
// Socket.io
9090
libraryDependencies += "io.socket" % "socket.io-client" % "1.0.0"
9191

92+
// Coursier
93+
libraryDependencies += "io.get-coursier" %% "coursier" % "2.0.0-RC3-2"
94+
9295
// ---------------------------------------------------------------------------------------------------------------------
9396
// PLUGIN FRAMEWORK DEFINITIONS
9497
// ---------------------------------------------------------------------------------------------------------------------

src/main/scala/org/codeoverflow/chatoverflow/framework/PluginType.scala

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
package org.codeoverflow.chatoverflow.framework
22

3+
import java.io.File
4+
35
import org.codeoverflow.chatoverflow.WithLogger
46
import org.codeoverflow.chatoverflow.api.APIVersion
57
import org.codeoverflow.chatoverflow.api.plugin.{Plugin, PluginManager}
68
import org.codeoverflow.chatoverflow.framework.PluginCompatibilityState.PluginCompatibilityState
79

10+
import scala.concurrent.Future
11+
812
/**
9-
* A plugin type is a container for all information about a plugin, everything in the 'plugin.xml' and the actual class.
10-
* The plugins functionality and meta information can be accessed through this interface.
11-
*
12-
* @param name the name of the plugin, used for identifying
13-
* @param author the author of the plugin, used for identifying
14-
* @param version the version of the plugin
15-
* @param majorAPIVersion the major api version, with which the plugin was developed
16-
* @param minorAPIVersion the minor api version, with which the plugin was developed
17-
* @param pluginClass the class of the plugin, used to create instances of this plugin.
18-
* Needs to have a constructor with the signature of one PluginManager,
19-
* otherwise instances can't be created from it.
20-
*/
13+
* A plugin type is a container for all information about a plugin, everything in the 'plugin.xml' and the actual class.
14+
* The plugins functionality and meta information can be accessed through this interface.
15+
*
16+
* @param name the name of the plugin, used for identifying
17+
* @param author the author of the plugin, used for identifying
18+
* @param version the version of the plugin
19+
* @param majorAPIVersion the major api version, with which the plugin was developed
20+
* @param minorAPIVersion the minor api version, with which the plugin was developed
21+
* @param pluginClass the class of the plugin, used to create instances of this plugin.
22+
* Needs to have a constructor with the signature of one PluginManager,
23+
* otherwise instances can't be created from it.
24+
* @param pluginDependencies A future that completes when all dependencies, that the plugin has, are available and
25+
* that returns a seq of the required dependencies files in the local coursier cache.
26+
*/
2127
class PluginType(name: String, author: String, version: String, majorAPIVersion: Int, minorAPIVersion: Int,
22-
metadata: PluginMetadata, pluginClass: Class[_ <: Plugin]) extends WithLogger {
28+
metadata: PluginMetadata, pluginClass: Class[_ <: Plugin], pluginDependencies: Future[Seq[File]]) extends WithLogger {
2329

2430
private var pluginVersionState = PluginCompatibilityState.Untested
2531

@@ -126,4 +132,12 @@ class PluginType(name: String, author: String, version: String, majorAPIVersion:
126132
* @return the PluginMetadata instance of this plugin
127133
*/
128134
def getMetadata: PluginMetadata = metadata
135+
136+
/**
137+
* Returns the future that will result in a sequence of jar files that represents the dependencies
138+
* of this plugin including all sub-dependencies.
139+
*
140+
* @return the dependency future
141+
*/
142+
def getDependencyFuture: Future[Seq[File]] = pluginDependencies
129143
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.codeoverflow.chatoverflow.framework.helper
2+
3+
import java.io.{File, InputStream}
4+
5+
import coursier.Fetch
6+
import coursier.cache.{CacheLogger, FileCache}
7+
import coursier.core.Dependency
8+
import coursier.maven.PomParser
9+
import org.codeoverflow.chatoverflow.WithLogger
10+
11+
import scala.io.Source
12+
13+
/**
14+
* A utility object containing some common code for use with Coursier.
15+
*/
16+
object CoursierUtils extends WithLogger {
17+
18+
private object CoursierLogger extends CacheLogger {
19+
override def downloadedArtifact(url: String, success: Boolean): Unit = {
20+
logger debug (if (success)
21+
s"Successfully downloaded $url"
22+
else
23+
s"Failed to download $url")
24+
}
25+
}
26+
27+
private val cache = FileCache().noCredentials.withLogger(CoursierLogger)
28+
29+
/**
30+
* Extracts all dependencies out of the provided pom. Throws an exception if the pom is invalid.
31+
*
32+
* @param is the InputStream from which the pom is read
33+
* @return a seq of all found dependencies
34+
*/
35+
def parsePom(is: InputStream): Seq[Dependency] = {
36+
val pomFile = Source.fromInputStream(is)
37+
val parser = coursier.core.compatibility.xmlParseSax(pomFile.mkString, new PomParser)
38+
39+
parser.project match {
40+
case Right(deps) => deps.dependencies.map(_._2)
41+
case Left(errorMsg) => throw new IllegalArgumentException(s"Pom couldn't be parsed: $errorMsg")
42+
}
43+
}
44+
45+
/**
46+
* Resolves and fetches all passed dependencies and gives back a seq of all local files of these dependencies.
47+
*
48+
* @param dependencies all dependencies that you want to be fetched
49+
* @return all local files for the passed dependencies
50+
*/
51+
def fetchDependencies(dependencies: Seq[Dependency]): Seq[File] = {
52+
// IntelliJ may warn you that a implicit is missing. This is one of the many bugs in IntelliJ, the code compiles fine.
53+
Fetch()
54+
.withCache(cache)
55+
.addDependencies(dependencies: _*)
56+
.run()
57+
}
58+
}

src/main/scala/org/codeoverflow/chatoverflow/framework/helper/PluginClassLoader.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package org.codeoverflow.chatoverflow.framework.helper
33
import java.net.{URL, URLClassLoader}
44

55
/**
6-
* This plugin class loader does only exist for plugin security policy checks.
7-
*
8-
* @param urls Takes an array of urls an creates a simple URLClassLoader with it
9-
*/
10-
class PluginClassLoader(urls: Array[URL]) extends URLClassLoader(urls)
6+
* This plugin class loader does only exist for plugin security policy checks and
7+
* to expose the addURL method to the package inorder to add all required dependencies after dependency resolution.
8+
*
9+
* @param urls Takes an array of urls an creates a simple URLClassLoader with it
10+
*/
11+
class PluginClassLoader(urls: Array[URL]) extends URLClassLoader(urls) {
12+
private[helper] override def addURL(url: URL): Unit = super.addURL(url) // just exposes this method to be package-private instead of protected
13+
}

src/main/scala/org/codeoverflow/chatoverflow/framework/helper/PluginLoader.scala

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import org.reflections.scanners.SubTypesScanner
1010
import org.reflections.util.ConfigurationBuilder
1111

1212
import scala.collection.JavaConverters._
13+
import scala.concurrent.ExecutionContext.Implicits.global
14+
import scala.concurrent.Future
15+
import scala.util.{Failure, Success}
1316
import scala.xml.{Node, SAXParseException, XML}
1417

1518
/**
@@ -80,7 +83,8 @@ class PluginLoader(private val jar: File) extends WithLogger {
8083
majorString.toInt,
8184
minorString.toInt,
8285
PluginMetadata.fromXML(p),
83-
cls
86+
cls,
87+
resolveDependencies(getString(p, "name"))
8488
))
8589
} catch {
8690
// thrown by getString
@@ -144,4 +148,29 @@ class PluginLoader(private val jar: File) extends WithLogger {
144148
None
145149
}
146150
}
151+
152+
/**
153+
* Creates a future which gets all dependencies from the included dependencies.pom, if existing, fetches them
154+
* and adds their jar files to the classloader.
155+
*
156+
* @param pluginName the name of the plugin, only used for logging
157+
* @return a future of all required jars for this plugin
158+
*/
159+
private def resolveDependencies(pluginName: String): Future[Seq[File]] = {
160+
val pomIs = classloader.getResourceAsStream("dependencies.pom")
161+
if (pomIs == null) {
162+
return Future(Seq())
163+
}
164+
165+
Future(CoursierUtils.parsePom(pomIs))
166+
.map(dependencies => dependencies.filter(_.module.name.value != "chatoverflow-api_2.12"))
167+
.map(dependencies => CoursierUtils.fetchDependencies(dependencies))
168+
.andThen {
169+
case Success(jarFiles) =>
170+
jarFiles.foreach(jar => classloader.addURL(jar.toURI.toURL))
171+
logger info s"Dependencies for the plugin $pluginName successfully resolved and fetched if missing."
172+
case Failure(exception) =>
173+
logger warn s"Couldn't resolve and fetch dependencies for the plugin in $pluginName: $exception"
174+
}
175+
}
147176
}

0 commit comments

Comments
 (0)