Skip to content

Commit 1396373

Browse files
authored
Merge pull request #32 from delphi-hub/Feature/27/NativeExe
Feature/27/native exe
2 parents 6ee305e + 7d8068a commit 1396373

File tree

10 files changed

+228
-229
lines changed

10 files changed

+228
-229
lines changed

build.sbt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ libraryDependencies += "de.vandermeer" % "asciitable" % "0.3.2"
2828
libraryDependencies += "com.lihaoyi" %% "fansi" % "0.2.5"
2929
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
3030
libraryDependencies += "au.com.bytecode" % "opencsv" % "2.4"
31+
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.4" % "test"
32+
33+
libraryDependencies ++= Seq(
34+
"com.softwaremill.sttp" %% "core" % "1.5.4",
35+
"com.softwaremill.sttp" %% "spray-json" % "1.5.4"
36+
)
37+
3138

3239
debianPackageDependencies := Seq("java8-runtime-headless")
3340

@@ -38,11 +45,23 @@ lazy val cli = (project in file(".")).
3845
enablePlugins(BuildInfoPlugin).
3946
enablePlugins(DebianPlugin).
4047
enablePlugins(WindowsPlugin).
41-
48+
enablePlugins(GraalVMNativeImagePlugin).
49+
settings(
50+
graalVMNativeImageOptions ++= Seq(
51+
"--enable-https",
52+
"--enable-http",
53+
"--enable-all-security-services",
54+
"--allow-incomplete-classpath",
55+
"--enable-url-protocols=http,https"
56+
)
57+
).
58+
enablePlugins(JDKPackagerPlugin).
4259
settings(
4360
buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion),
44-
buildInfoPackage := "de.upb.cs.swt.delphi.cli"
61+
buildInfoPackage := "de.upb.cs.swt.delphi.cli",
4562
)
4663
scalastyleConfig := baseDirectory.value / "project" / "scalastyle-config.xml"
4764

4865
trapExit := false
66+
fork := true
67+
connectInput := true

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version = 1.1.1
1+
sbt.version = 1.2.8

project/plugins.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// build management and packaging
22
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0")
3-
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.2")
3+
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15")
44

55
// coverage
66
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")

src/main/scala/de/upb/cs/swt/delphi/cli/Config.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ package de.upb.cs.swt.delphi.cli
2323
* @param verbose Marker if logging should be verbose
2424
* @param mode The command to be run
2525
*/
26-
case class Config(server: String = sys.env.getOrElse("DELPHI_SERVER", "https://delphi.cs.uni-paderborn.de/api/"),
26+
case class Config(server: String = sys.env.getOrElse("DELPHI_SERVER", "https://delphi.cs.uni-paderborn.de/api"),
2727
verbose: Boolean = false,
2828
raw: Boolean = false,
2929
csv: String = "",

src/main/scala/de/upb/cs/swt/delphi/cli/DelphiCLI.scala

Lines changed: 61 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,81 +16,87 @@
1616

1717
package de.upb.cs.swt.delphi.cli
1818

19-
import akka.actor.ActorSystem
20-
import akka.http.scaladsl.Http
21-
import de.upb.cs.swt.delphi.cli.commands.{RetrieveCommand, SearchCommand, TestCommand}
22-
23-
import scala.concurrent.duration.Duration
24-
import scala.concurrent.{Await, ExecutionContext}
25-
19+
import com.softwaremill.sttp._
20+
import de.upb.cs.swt.delphi.cli.commands._
2621

2722
/**
2823
* The application class for the Delphi command line interface
2924
*/
30-
object DelphiCLI extends App {
25+
object DelphiCLI {
26+
27+
28+
def main(args: Array[String]): Unit = {
3129

32-
implicit val system = ActorSystem()
30+
def getEnvOrElse(envVar: String, defaultPath: String) = sys.env.getOrElse(envVar, defaultPath)
3331

34-
val cliParser = {
35-
new scopt.OptionParser[Config]("delphi-cli") {
36-
head("Delphi Command Line Tool", s"(${BuildInfo.version})")
32+
val javaLibPath = getEnvOrElse("JAVA_LIB_PATH", "/usr/lib/jvm/default-java/lib/")
3733

38-
version("version").text("Prints the version of the command line tool.")
34+
val trustStorePath = getEnvOrElse("JAVA_TRUSTSTORE", "/usr/lib/jvm/default-java/lib/security/cacerts")
3935

40-
help("help").text("Prints this help text.")
41-
override def showUsageOnError = true
36+
System.setProperty("java.library.path", javaLibPath)
37+
System.setProperty("javax.net.ssl.trustStore", trustStorePath)
4238

43-
opt[String]("server").action( (x,c) => c.copy(server = x)).text("The url to the Delphi server")
44-
opt[Unit] (name = "raw").action((_,c) => c.copy(raw = true)).text("Output the raw results")
45-
opt[Unit] (name = "silent").action((_,c) => c.copy(silent = true)).text("Suppress non-result output")
39+
cliParser.parse(args, Config()) match {
40+
case Some(c) =>
4641

47-
checkConfig(c => if (c.server.isEmpty()) failure("Option server is required.") else success)
4842

49-
cmd("test").action((_,c) => c.copy(mode = "test"))
43+
implicit val config: Config = c
44+
implicit val backend: SttpBackend[Id, Nothing] = HttpURLConnectionBackend()
5045

51-
cmd("retrieve").action((s,c) => c.copy(mode = "retrieve"))
52-
.text("Retrieve a project's description, specified by ID.")
53-
.children(
54-
arg[String]("id").action((x, c) => c.copy(id = x)).text("The ID of the project to retrieve"),
55-
opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"),
56-
opt[Unit]('f', "file").action((_, c) => c.copy(opts = List("file"))).text("Use to load the ID from file, " +
57-
"with the filepath given in place of the ID")
58-
)
46+
if (!config.silent) cliParser.showHeader()
5947

60-
cmd("search").action((s, c) => c.copy(mode = "search"))
61-
.text("Search artifact using a query.")
62-
.children(
63-
arg[String]("query").action((x,c) => c.copy(query = x)).text("The query to be used."),
64-
opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"),
65-
opt[Int]("limit").action((x, c) => c.copy(limit = Some(x))).text("The maximal number of results returned."),
66-
opt[Unit](name="list").action((_, c) => c.copy(list = true)).text("Output results as list (raw option overrides this)"),
67-
opt[Int]("timeout").action((x, c) => c.copy(timeout = Some(x))).text("Timeout in seconds.")
68-
)
48+
config.mode match {
49+
case "test" => TestCommand.execute
50+
case "retrieve" => RetrieveCommand.execute
51+
case "search" => SearchCommand.execute
52+
case x => config.consoleOutput.outputError(s"Unknown command: $x")
53+
}
54+
55+
56+
case None =>
6957
}
58+
7059
}
7160

61+
private def cliParser = {
62+
val parser = {
63+
new scopt.OptionParser[Config]("delphi-cli") {
64+
head("Delphi Command Line Tool", s"(${BuildInfo.version})")
7265

73-
cliParser.parse(args, Config()) match {
74-
case Some(config) =>
75-
if (!config.silent) cliParser.showHeader()
76-
config.mode match {
77-
case "test" => TestCommand.execute(config)
78-
case "retrieve" => RetrieveCommand.execute(config)
79-
case "search" => SearchCommand.execute(config)
80-
case x => config.consoleOutput.outputError(s"Unknown command: $x")
81-
}
66+
version("version").text("Prints the version of the command line tool.")
8267

83-
case None =>
84-
}
68+
help("help").text("Prints this help text.")
69+
70+
override def showUsageOnError = true
8571

72+
opt[String]("server").action((x, c) => c.copy(server = x)).text("The url to the Delphi server")
73+
opt[Unit](name = "raw").action((_, c) => c.copy(raw = true)).text("Output the raw results")
74+
opt[Unit](name = "silent").action((_, c) => c.copy(silent = true)).text("Suppress non-result output")
8675

87-
val poolShutdown = Http().shutdownAllConnectionPools()
88-
Await.result(poolShutdown, Duration.Inf)
76+
checkConfig(c => if (c.server.isEmpty()) failure("Option server is required.") else success)
8977

90-
implicit val ec: ExecutionContext = system.dispatcher
91-
val terminationFuture = system.terminate()
78+
cmd("test").action((_, c) => c.copy(mode = "test"))
9279

93-
terminationFuture.onComplete {
94-
sys.exit(0)
80+
cmd("retrieve").action((s, c) => c.copy(mode = "retrieve"))
81+
.text("Retrieve a project's description, specified by ID.")
82+
.children(
83+
arg[String]("id").action((x, c) => c.copy(id = x)).text("The ID of the project to retrieve"),
84+
opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"),
85+
opt[Unit]('f', "file").action((_, c) => c.copy(opts = List("file"))).text("Use to load the ID from file, " +
86+
"with the filepath given in place of the ID")
87+
)
88+
89+
cmd("search").action((s, c) => c.copy(mode = "search"))
90+
.text("Search artifact using a query.")
91+
.children(
92+
arg[String]("query").action((x, c) => c.copy(query = x)).text("The query to be used."),
93+
opt[String]("csv").action((x, c) => c.copy(csv = x)).text("Path to the output .csv file (overwrites existing file)"),
94+
opt[Int]("limit").action((x, c) => c.copy(limit = Some(x))).text("The maximal number of results returned."),
95+
opt[Unit](name = "list").action((_, c) => c.copy(list = true)).text("Output results as list (raw option overrides this)"),
96+
opt[Int]("timeout").action((x, c) => c.copy(timeout = Some(x))).text("Timeout in seconds.")
97+
)
98+
}
99+
}
100+
parser
95101
}
96-
}
102+
}

src/main/scala/de/upb/cs/swt/delphi/cli/commands/Command.scala

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,68 +16,53 @@
1616

1717
package de.upb.cs.swt.delphi.cli.commands
1818

19-
import akka.actor.ActorSystem
20-
import akka.http.scaladsl.Http
21-
import akka.http.scaladsl.model.Uri.Query
22-
import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes, Uri}
23-
import akka.stream.ActorMaterializer
24-
import akka.util.ByteString
19+
import com.softwaremill.sttp._
2520
import de.upb.cs.swt.delphi.cli.Config
2621

27-
import scala.concurrent.{Await, Future}
28-
import scala.concurrent.duration._
29-
import scala.util.{Failure, Success}
30-
3122
/**
3223
* Represents the implementation of a command of the CLI
3324
*/
3425
trait Command {
3526

3627
/**
3728
* Executes the command implementation
29+
*
3830
* @param config The current configuration for the command
3931
*/
40-
def execute(config: Config)(implicit system : ActorSystem): Unit
32+
def execute(implicit config: Config, backend: SttpBackend[Id, Nothing]): Unit = {}
33+
4134

4235
/**
43-
* Implements a common request type using currying to avoid code duplication
44-
* @param target The endpoint to perform a Get request on
45-
* @param config The current configuration for the command
36+
* Http GET request template
37+
*
38+
* @param target Sub url in delphi server
39+
* @param parameters Query params
40+
* @return GET response
4641
*/
47-
protected def executeGet(target: String, parameters: Map[String, String] = Map())(config: Config, system : ActorSystem) : Option[String] = {
48-
implicit val sys : ActorSystem = system
49-
implicit val materializer = ActorMaterializer()
50-
implicit val executionContext = sys.dispatcher
51-
52-
val uri = Uri(config.server)
53-
config.consoleOutput.outputInformation(s"Contacting server ${uri}...")
54-
55-
val responseFuture = Http().singleRequest(HttpRequest(uri = uri.withPath(uri.path + target).withQuery(Query(parameters))))
56-
57-
responseFuture.onComplete {
58-
case Failure(_) => error(config)(s"Could not reach server ${config.server}.")
59-
case _ =>
42+
protected def executeGet(paths: Seq[String], parameters: Map[String, String] = Map())
43+
(implicit config: Config, backend: SttpBackend[Id, Nothing]): Option[String] = {
44+
val serverUrl = uri"${config.server}"
45+
val oldPath = serverUrl.path
46+
val reqUrl = serverUrl.path(oldPath ++ paths).params(parameters)
47+
val request = sttp.get(reqUrl)
48+
config.consoleOutput.outputInformation(s"Sending request ${request.uri}")
49+
val response = request.send()
50+
response.body match {
51+
case Left(value) =>
52+
error.apply(s"Request failed:\n $value")
53+
None
54+
case Right(value) =>
55+
Some(value)
6056
}
61-
62-
val result = Await.result(responseFuture, 30 seconds)
63-
val resultString = result match {
64-
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
65-
entity.dataBytes.runFold(ByteString(""))(_ ++ _).map { body =>
66-
Some(body.utf8String)
67-
}
68-
case resp @ HttpResponse(code, _, _, _) => {
69-
error(config)("Artifact not found.")
70-
resp.discardEntityBytes()
71-
Future(None)
72-
}
73-
}
74-
75-
Await.result(resultString, Duration.Inf)
7657
}
7758

59+
7860
protected def information(implicit config: Config): String => Unit = config.consoleOutput.outputInformation _
61+
7962
protected def reportResult(implicit config: Config): Any => Unit = config.consoleOutput.outputResult _
63+
8064
protected def error(implicit config: Config): String => Unit = config.consoleOutput.outputError _
65+
8166
protected def success(implicit config: Config): String => Unit = config.consoleOutput.outputSuccess _
8267

8368
protected def exportResult(implicit config: Config): Any => Unit = config.csvOutput.exportResult _

src/main/scala/de/upb/cs/swt/delphi/cli/commands/RetrieveCommand.scala

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,22 @@
1616

1717
package de.upb.cs.swt.delphi.cli.commands
1818

19-
import akka.actor.ActorSystem
20-
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
21-
import akka.http.scaladsl.unmarshalling.Unmarshal
22-
import akka.stream.ActorMaterializer
23-
import de.upb.cs.swt.delphi.cli.Config
19+
import com.softwaremill.sttp.{Id, SttpBackend}
20+
import de.upb.cs.swt.delphi.cli._
2421
import de.upb.cs.swt.delphi.cli.artifacts.RetrieveResult
2522
import de.upb.cs.swt.delphi.cli.artifacts.SearchResultJson._
26-
import de.upb.cs.swt.delphi.cli.commands.SearchCommand.information
27-
import spray.json.DefaultJsonProtocol
23+
import spray.json._
2824

29-
import scala.concurrent.Await
30-
import scala.concurrent.duration.Duration
3125
import scala.io.Source
32-
import scala.util.{Failure, Success}
3326

3427
/**
3528
* The implementation of the retrieve command.
3629
* Retrieves the contents of the file at the endpoint specified by the config file, and prints them to stdout
3730
*/
38-
object RetrieveCommand extends Command with SprayJsonSupport with DefaultJsonProtocol {
31+
object RetrieveCommand extends Command {
3932

4033

41-
override def execute(config: Config)(implicit system: ActorSystem): Unit = {
42-
implicit val ec = system.dispatcher
43-
implicit val materializer = ActorMaterializer()
34+
override def execute(implicit config: Config, backend: SttpBackend[Id, Nothing]): Unit = {
4435

4536
//Checks whether the ID should be loaded from a file or not, and either returns the first line
4637
// of the given file if it is, or the specified ID otherwise
@@ -56,35 +47,27 @@ object RetrieveCommand extends Command with SprayJsonSupport with DefaultJsonPro
5647
}
5748

5849
val result = executeGet(
59-
s"/retrieve/$checkTarget",
50+
Seq("retrieve", checkTarget),
6051
Map("pretty" -> "")
61-
)(config, system)
52+
)
6253

63-
result.map(s => {
54+
result.foreach(s => {
6455
if (config.raw) {
65-
reportResult(config)(s)
56+
reportResult.apply(s)
6657
}
67-
6858
if (!config.raw || !config.csv.equals("")) {
69-
val unmarshalledFuture = Unmarshal(s).to[List[RetrieveResult]]
7059

71-
unmarshalledFuture.transform {
72-
case Success(unmarshalled) => {
73-
val unmarshalled = Await.result(unmarshalledFuture, Duration.Inf)
74-
success(config)(s"Found ${unmarshalled.size} item(s).")
75-
reportResult(config)(unmarshalled)
60+
//TODO: Direct convertTo[List[RetrieveResult]] not working ???
61+
7662

77-
if(!config.csv.equals("")) {
78-
exportResult(config)(unmarshalled)
79-
information(config)("Results written to file '" + config.csv + "'")
80-
}
63+
val jsonArr = s.parseJson.asInstanceOf[JsArray].elements
64+
val retrieveResults = jsonArr.map(r => r.convertTo[RetrieveResult])
8165

82-
Success(unmarshalled)
83-
}
84-
case Failure(e) => {
85-
error(config)(s)
86-
Failure(e)
87-
}
66+
success.apply(s"Found ${retrieveResults.size} item(s).")
67+
reportResult.apply(retrieveResults)
68+
if (!config.csv.equals("")) {
69+
exportResult.apply(retrieveResults)
70+
information.apply("Results written to file '" + config.csv + "'")
8871
}
8972
}
9073
})

0 commit comments

Comments
 (0)