Skip to content

Commit

Permalink
Split sailgun into core and cli
Browse files Browse the repository at this point in the history
* Implement CLI with scopt
* Add travis CI checks
* Redesign of the build definition
  • Loading branch information
jvican committed Jun 26, 2019
1 parent 0193a2a commit 37d23eb
Show file tree
Hide file tree
Showing 28 changed files with 205 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .java-version
@@ -0,0 +1,2 @@
oracle64-1.8.0.212
1.8
1 change: 1 addition & 0 deletions .scalafmt.conf
@@ -1 +1,2 @@
version = "2.0.0-RC4"
maxColumn = 100
38 changes: 38 additions & 0 deletions .travis.yml
@@ -0,0 +1,38 @@
language: scala
scala: 2.12.8
os: linux

addons:
apt:
packages:
- fakeroot

sudo: false
cache:
apt: true
directories:
- $HOME/.sbt
- $HOME/.m2
- $HOME/.ivy2
- $HOME/.jabba

env:
global:
- TRAVIS_JDK=zulu@1.8.212
- JABBA_HOME=$HOME/.jabba

before_install:
- $JABBA_HOME/bin/jabba install graalvm@19.0.2
if [[ "$TRAVIS_OS_NAME" = "linux" ]]; then
export JAVA_HOME="$JABBA_HOME/jdk/graalvm@19.0.2" && export PATH="$JAVA_HOME/bin:$PATH" && java -version;
elif [[ "$TRAVIS_OS_NAME" = "osx" ]]; then
export JAVA_HOME="$JABBA_HOME/jdk/graalvm@19.0.2/Contents/Home" && export PATH="$JAVA_HOME/bin:$PATH" && java -version;
fi
gu install native-image

jobs:
include:
- script: |
sbt "test" "sailgun-cli/graalvm-native-image:packageBin"
if: type = pull_request OR (type = push AND branch = master)
name: "Run tests and test GraalVM Native Image"
28 changes: 21 additions & 7 deletions build.sbt
@@ -1,21 +1,35 @@
import build.BuildKeys._
import build.Dependencies

lazy val sailgun = project
.in(file("."))
.enablePlugins(GraalVMNativeImagePlugin)
lazy val `sailgun-core` = project
.in(file("core"))
.settings(testSuiteSettings)
.settings(
name := "sailgun",
fork in run in Compile := true,
fork in test in Test := true,
libraryDependencies ++= Seq(
Dependencies.jna,
Dependencies.jnaPlatform
),
)
)

lazy val `sailgun-cli` = project
.in(file("cli"))
.dependsOn(`sailgun-core`)
.enablePlugins(GraalVMNativeImagePlugin)
.settings(testSuiteSettings)
.settings(
fork in run in Compile := true,
fork in test in Test := true,
libraryDependencies ++= List(Dependencies.scopt),
graalVMNativeImageOptions ++= List(
"--no-fallback",
"-H:+ReportExceptionStackTraces",
"-H:Log=registerResource",
"-H:IncludeResources=com/sun/jna/darwin/libjnidispatch.jnilib"
// Required by GraalVM Native Image, otherwise error
"--initialize-at-build-time=scala.Function1"
)
)

lazy val sailgun = project
.in(file("."))
.aggregate(`sailgun-core`, `sailgun-cli`)
114 changes: 114 additions & 0 deletions cli/src/main/scala/sailgun/Cli.scala
@@ -0,0 +1,114 @@
package sailgun

import java.io.PrintStream
import java.io.InputStream
import java.util.concurrent.atomic.AtomicBoolean

import sailgun.protocol.Defaults
import sailgun.protocol.Streams
import sailgun.logging.SailgunLogger

import scopt.OParser
import java.net.ConnectException

/**
* An implementation of a CLI via case-app.
*
* Unfortunately, GraalVM Native Image doesn't correctly generate a native
* image because of parsing errors and warnings generated by the use of macros
* in case-app via Shapeless. For that reason, this class is left here but it's
* not used by default, preferring a CLI implementation that requires no macros
* and works with GraalVM Native Image.
*/
abstract class Cli(in: InputStream, out: PrintStream, err: PrintStream) {
def exit(code: Int): Unit
def run(args: Array[String]): Unit = {
var setServer: Boolean = false
var setPort: Boolean = false
val cliParser = {
val builder = OParser.builder[CliParams]
val nailgunServerOpt = builder
.opt[String]("nailgun-server")
.action((server, params) => { setServer = true; params.copy(nailgunServer = server) })
.text("Specify the host name of the target Nailgun server")
val nailgunPortOpt = builder
.opt[Int]("nailgun-port")
.action((port, params) => { setPort = true; params.copy(nailgunPort = port) })
.text("Specify the port of the target Nailgun server")
val helpOpt = builder
.opt[Unit]('h', "help")
.action((_, params) => params.copy(help = true))
.text("Print help of the Nailgun server")
val nailgunShowVersionOpt = builder
.opt[Unit]("nailgun-showversion")
.action((_, params) => params.copy(nailgunShowVersion = true))
.text("Print version of Nailgun client before running command")
val nailgunHelpOpt = builder
.help("nailgun-help")
.text("Print help of the Nailgun client")
val verboseOpt = builder
.opt[Unit]("verbose")
.action((_, params) => params.copy(verbose = true))
.text("Enable verbosity of the Nailgun client")
val cmdOpt = builder
.arg[String]("<cmd>...")
.optional()
.unbounded()
.action((arg, params) => params.copy(args = arg +: params.args))
.text("The command and arguments for the Nailgun server")
OParser
.sequence(
builder.programName("nailgun"),
builder.head("nailgun", Defaults.Version),
nailgunServerOpt,
nailgunPortOpt,
helpOpt,
nailgunHelpOpt,
nailgunShowVersionOpt,
verboseOpt,
cmdOpt
)
}

def errorAndExit(msg: String): Unit = { err.println(msg); exit(1) }
OParser.parse(cliParser, args, CliParams()) match {
case None => exit(1)
case Some(params) =>
if (params.nailgunShowVersion)
out.println(s"Nailgun v${Defaults.Version}")

def process(cmd: String, cmdArgs: Array[String]) = {
val streams = Streams(in, out, err)
val hostServer =
if (setServer) params.nailgunServer
else Defaults.env.getOrElse("NAILGUN_SERVER", params.nailgunServer)
val portServer =
if (setPort) params.nailgunPort
else Defaults.env.getOrElse("NAILGUN_PORT", params.nailgunPort.toString).toInt
val client = TcpClient(hostServer, portServer)
val noCancel = new AtomicBoolean(false)
val logger = new SailgunLogger("log", out, isVerbose = params.verbose)
try {
val code =
client.run(cmd, cmdArgs, Defaults.cwd, Defaults.env, streams, logger, noCancel)
logger.debug(s"Return code is $code")
exit(code)
} catch {
case _: ConnectException =>
errorAndExit(s"No server running in $hostServer:$portServer!")
}
}

params.args match {
case Nil if params.help => process("help", Array.empty)
case Nil => errorAndExit("Missing command for Nailgun server!")
case cmd :: cmdArgs => process(cmd, cmdArgs.toArray)
}
}
}
}

object Cli extends Cli(System.in, System.out, System.err) {
def main(args: Array[String]): Unit = run(args)
override def exit(code: Int) = System.exit(code)
}
13 changes: 13 additions & 0 deletions cli/src/main/scala/sailgun/CliParams.scala
@@ -0,0 +1,13 @@
package sailgun

import sailgun.protocol.Defaults

final case class CliParams(
nailgunServer: String = Defaults.Host,
nailgunPort: Int = Defaults.Port,
help: Boolean = false,
nailgunHelp: Boolean = false,
verbose: Boolean = false,
nailgunShowVersion: Boolean = false,
args: List[String] = Nil
)
File renamed without changes.
File renamed without changes.
Expand Up @@ -52,23 +52,4 @@ object TcpClient {
def apply(host: String, port: Int): TcpClient = {
new TcpClient(InetAddress.getByName(host), port)
}

def main(args: Array[String]): Unit = {
val client = TcpClient(Defaults.Host, Defaults.Port)
val streams = Streams(System.in, System.out, System.err)
val logger = new SailgunLogger("tcp-logger", System.out, isVerbose = false)

val code = client.run(
"about",
new Array(0),
Defaults.cwd,
Defaults.env,
streams,
logger,
new AtomicBoolean(false)
)

logger.debug(s"Return code is $code")
System.exit(code)
}
}
File renamed without changes.
Expand Up @@ -7,7 +7,7 @@ class SailgunLogger(
out: PrintStream,
override val isVerbose: Boolean
) extends Logger {
def debug(msg: String): Unit = out.println(s"debug: $msg")
def debug(msg: String): Unit = if (isVerbose) out.println(s"debug: $msg") else ()
def error(msg: String): Unit = out.println(s"error: $msg")
def warn(msg: String): Unit = out.println(s"warn: $msg")
def info(msg: String): Unit = out.println(s"$msg")
Expand Down
Expand Up @@ -5,7 +5,7 @@ import java.nio.file.Paths
object Defaults {
val Version = "0.9.3"
val Host = "127.0.0.1"
val Port = 8313
val Port = 2113

val env: Map[String, String] = {
import scala.collection.JavaConverters._
Expand Down
File renamed without changes.
Expand Up @@ -244,16 +244,19 @@ class SailgunBaseSuite extends BaseSuite {
in: InputStream = System.in
)(op: TestInputs => Unit): Unit = {
val logger = new RecordingLogger()
val stop = new AtomicBoolean(false)
test(testName) {
try {
val stop = new AtomicBoolean(false)
val out = new ByteArrayOutputStream()
val streams = Streams(in, out, out)
withRunningServer(streams, logger) { client =>
op(TestInputs(streams, logger, stop, client, out))
}
} catch {
case t: TimeoutException => logger.dump(oldErr); throw t
case t: TimeoutException =>
logger.dump(oldErr);
stop.set(true)
throw t
}
}
}
Expand Down
Expand Up @@ -25,10 +25,13 @@ object SailgunSpec extends SailgunBaseSuite {
val args = Array("2000")
val code = inputs.run("heartbeat", args)
assert(code == 0)
// In 2000ms, we can receive 3 'H'
// Compute how many 'H's we should expect
val counterForH =
inputs.logger.getMessagesAt(Some("debug")).count(_.contains("Got client heartbeat"))
assert(counterForH > 0)
assertNoDiff(
inputs.generateResult,
"HHH"
List.fill(counterForH)("H").mkString
)
}

Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 4 additions & 0 deletions project/Dependencies.scala
Expand Up @@ -6,6 +6,8 @@ object Dependencies {
val jnaVersion = "4.5.0"
val nailgunVersion = "ee3c4343"
val difflibVersion = "1.3.0"
val caseAppVersion = "1.2.0-faster-compile-time"
val shapelessVersion = "2.3.3-lower-priority-coproduct"

val monix = "io.monix" %% "monix" % "2.3.3"
val utest = "com.lihaoyi" %% "utest" % "0.6.6"
Expand All @@ -16,4 +18,6 @@ object Dependencies {
val nailgun = "ch.epfl.scala" % "nailgun-server" % nailgunVersion
val nailgunExamples = "ch.epfl.scala" % "nailgun-examples" % nailgunVersion
val difflib = "com.googlecode.java-diff-utils" % "diffutils" % difflibVersion

val scopt = "com.github.scopt" %% "scopt" % "4.0.0-RC2"
}

0 comments on commit 37d23eb

Please sign in to comment.