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

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
sebinside committed Oct 24, 2019
2 parents ea47ba6 + 8618d88 commit 00d8b34
Show file tree
Hide file tree
Showing 12 changed files with 563 additions and 202 deletions.
21 changes: 14 additions & 7 deletions bootstrap/build.sbt → bootstrap/launcher/build.sbt
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
name := "chatoverflow-bootstrap"
version := "0.1"
assemblyJarName in assembly := "ChatOverflow.jar"

// JLine is used for terminal width
libraryDependencies += "org.jline" % "jline-terminal-jansi" % "3.11.0"
name := "chatoverflow-bootstrap-launcher"
// s"$version-$versionSuffix" has to represent the tag name of the current release inorder for the Updater to know the current version.
version := "0.3"
lazy val versionSuffix = "prealpha"
assemblyJarName in assembly := "ChatOverflow-Launcher.jar"

// Coursier is used to download the deps of the framework
// Excluding argonaut and it's dependencies because we don't use any json with Coursier and that way we are able
Expand All @@ -15,6 +14,14 @@ libraryDependencies += "io.get-coursier" %% "coursier" % "2.0.0-RC3-2" excludeAl
ExclusionRule(organization = "com.github.alexarchambault", name = "argonaut-shapeless_6.2_2.12")
)

// Command Line Parsing
libraryDependencies += "com.github.scopt" %% "scopt" % "3.5.0"

fork := true

packageBin / includePom := false
packageBin / includePom := false

Compile / compile := {
IO.write((Compile / classDirectory).value / "version.txt", version.value + "-" + versionSuffix)
(Compile / compile).value
}
117 changes: 117 additions & 0 deletions bootstrap/launcher/src/main/scala/Bootstrap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import java.io.File

import CLI._

/**
* The bootstrap launcher downloads all required libraries and starts chat overflow with the correct parameters.
*/
object Bootstrap {

// Java home path (jre installation folder)
private val javaHomePath: String = System.getProperty("java.home")

// Chat Overflow Launcher / Main class (should not change anymore)
private val chatOverflowMainClass = "org.codeoverflow.chatoverflow.Launcher"

/**
* Launcher entry point.
* Validates installation, downloads dependencies and start ChatOverflow.
*
* @param args the arguments, which are passed to ChatOverflow
*/
def main(args: Array[String]): Unit = {
val conf: Config = ArgsParser.parse(args, Config()) match {
case Some(value) => value
case None => System.exit(1); null
}

if (testValidity(conf.directory)) {
println("Valid ChatOverflow installation. Checking libraries...")

val deps = new DependencyDownloader(conf.directory).fetchDependencies().map(u => new File(u.getFile))
if (deps.nonEmpty) {
val javaPath = createJavaPath()
if (javaPath.isDefined) {
println("Found java installation. Starting ChatOverflow...")

// Start chat overflow!
val command = List(javaPath.get, "-cp", s"bin/*${deps.mkString(File.pathSeparator, File.pathSeparator, "")}", chatOverflowMainClass) ++ args
val processBuilder = new java.lang.ProcessBuilder(command: _*)
.inheritIO().directory(new File(conf.directory))

processBuilder.environment().put("CHATOVERFLOW_BOOTSTRAP", "true")

val process = processBuilder.start()

val exitCode = process.waitFor()
println(s"ChatOverflow stopped with exit code: $exitCode")
} else {
println("Unable to find java installation. Unable to start.")
}
} else {
println("Error: Problem with libraries. Unable to start.")
}
} else {
println("Error: Invalid ChatOverflow installation. Please extract all provided files properly. Unable to start.")
}
}

/**
* Takes the java home path of the launcher and tries to find the java(.exe)
*
* @return the path to the java runtime or none, if the file was not found
*/
private def createJavaPath(): Option[String] = {

// Check validity of java.home path first
if (!new File(javaHomePath).exists()) {
None
} else {

// Check for windows and unix java versions
// This should work on current and older java JRE/JDK installations,
// see: https://stackoverflow.com/questions/52584888/how-to-use-jdk-without-jre-in-java-11
val javaExePath = s"$javaHomePath/bin/java.exe"
val javaPath = s"$javaHomePath/bin/java"

if (new File(javaExePath).exists()) {
Some(javaExePath)
} else if (new File(javaPath).exists()) {
Some(javaPath)
} else {
None
}
}

}

/**
* Checks, if the installation is valid
*/
private def testValidity(currentFolderPath: String): Boolean = {
// The first check is the existence of a bin folder
val binDir = new File(s"$currentFolderPath/bin")
check(binDir.exists() && binDir.isDirectory, "The bin directory doesn't exist") && {
// Next are the existence of a framework, api and gui jar
val jars = binDir.listFiles().filter(_.getName.endsWith(".jar"))

check(jars.exists(_.getName.toLowerCase.startsWith("chatoverflow_")), "There is no api jar in the bin directory.") &&
check(jars.exists(_.getName.toLowerCase.startsWith("chatoverflow-api")), "There is no api jar in the bin directory.") &&
check(jars.exists(_.getName.toLowerCase.startsWith("chatoverflow-gui")),
"Note: No gui jar detected. The ChatOverflow gui won't be usable.", required = false)
}
}

/**
* Helper method for [[Bootstrap.testValidity()]]. Checks condition, prints description if the condition is false and
* returns false if the condition is false and the check is required.
*/
private def check(condition: Boolean, description: String, required: Boolean = true): Boolean = {
if (condition) {
true
} else {
println(description)
!required
}
}
}
35 changes: 35 additions & 0 deletions bootstrap/launcher/src/main/scala/CLI.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import java.io.File
import java.nio.file.Paths


object CLI {

/**
* Everything in here also has to be defined in the CLI class of the framework, because
* all arguments including bootstrap specific ones are passed through to the framework.
* Filtering these options out would be way too difficult, because you need to know if a option
* is a simple flag or is followed by a value. Scopt doesn't expose anything to get this so we would
* need to use reflect, which is very ugly.
* This, while not that elegant as I would like it to be, is just simple and works.
*/
object ArgsParser extends scopt.OptionParser[Config]("ChatOverflow Launcher") {
opt[File]("directory")
.action((x, c) => c.copy(directory = x.getAbsolutePath))
.text("The directory in which ChatOverflow will be executed")
.validate(f =>
if (!f.exists())
Left("Directory doesn't exist")
else if (!f.isDirectory)
Left("Path isn't a directory")
else
Right()
)

override def errorOnUnknownArgument: Boolean = false

override def reportWarning(msg: String): Unit = ()
}

case class Config(directory: String = Paths.get("").toAbsolutePath.toString)

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import java.io.{File, InputStream}
import java.net.URL
import java.net.{URL, URLClassLoader}

import Bootstrap.classloader
import coursier.Fetch
import coursier.cache.FileCache
import coursier.cache.loggers.{FileTypeRefreshDisplay, RefreshLogger}
Expand All @@ -10,19 +9,26 @@ import coursier.maven.{MavenRepository, PomParser}

import scala.io.Source

object DependencyDownloader {
class DependencyDownloader(directory: String) {
private val pomFile = "dependencies.pom"
private val logger = RefreshLogger.create(System.out, FileTypeRefreshDisplay.create())
private val cache = FileCache().noCredentials.withLogger(logger)

// Classloader containing all jars, used to get the dependencies from the framework jar
private val jarFiles = {
val jarsOpt = Option(new File(s"$directory/bin").listFiles())
jarsOpt.getOrElse(Array()).filter(_.getName.endsWith(".jar")).map(_.toURI.toURL)
}
private val classloader = new URLClassLoader(jarFiles)

private def getPomIs: InputStream = classloader.getResourceAsStream(pomFile)

/**
* Parses the pom file of the framework jar and returns a seq of all dependencies that are required to run it.
*
* @return the seq of dependencies, if it is empty an error has occurred and logged.
*/
def parsePom(): Seq[Dependency] = {
private def parsePom(): Seq[Dependency] = {
if (getPomIs == null) {
println("Couldn't find the pom containing all required dependencies for the framework in the jar.")
return Seq()
Expand Down
94 changes: 0 additions & 94 deletions bootstrap/src/main/scala/Bootstrap.scala

This file was deleted.

Loading

0 comments on commit 00d8b34

Please sign in to comment.