Skip to content

Commit

Permalink
Merge pull request #551 from tindzk/main-classes
Browse files Browse the repository at this point in the history
Add ability to set default main class
  • Loading branch information
jvican authored Jul 3, 2018
2 parents e0f04c2 + 24bacd5 commit 926926b
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 66 deletions.
53 changes: 36 additions & 17 deletions config/src/main/scala-2.10/bloop/config/ConfigEncoderDecoders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ object ConfigEncoderDecoders {
case JavaThenScala.id => Right(JavaThenScala)
case ScalaThenJava.id => Right(ScalaThenJava)
case _ =>
val msg = s"Expected compile order ${CompileOrder.All.map(s => s"'$s'").mkString(", ")})."
val msg = s"Expected compile order ${CompileOrder.All.map(s => s"'$s'").mkString(", ")})"
Left(DecodingFailure(msg, c.history))
}
}
Expand All @@ -63,7 +63,7 @@ object ConfigEncoderDecoders {
case Debug.id => Right(Debug)
case Release.id => Right(Release)
case _ =>
val msg = s"Expected linker mode ${LinkerMode.All.map(s => s"'$s'").mkString(", ")})."
val msg = s"Expected linker mode ${LinkerMode.All.map(s => s"'$s'").mkString(", ")})"
Left(DecodingFailure(msg, c.history))
}
}
Expand All @@ -82,7 +82,7 @@ object ConfigEncoderDecoders {
case ModuleKindJS.NoModule.id => Right(ModuleKindJS.NoModule)
case ModuleKindJS.CommonJSModule.id => Right(ModuleKindJS.CommonJSModule)
case _ =>
val msg = s"Expected module kind ${ModuleKindJS.All.map(s => s"'$s'").mkString(", ")})."
val msg = s"Expected module kind ${ModuleKindJS.All.map(s => s"'$s'").mkString(", ")})"
Left(DecodingFailure(msg, c.history))
}
}
Expand All @@ -102,29 +102,48 @@ object ConfigEncoderDecoders {

private final val N = "name"
private final val C = "config"
private final val M = "mainClass"

implicit val platformEncoder: RootEncoder[Platform] = new RootEncoder[Platform] {
override final def apply(platform: Platform): Json = platform match {
case Platform.Jvm(config) =>
val configJson = Json.fromJsonObject(jvmEncoder.encodeObject(config))
Json.fromFields(List((N, Json.fromString(Platform.Jvm.name)), (C, configJson)))
case Platform.Js(config) =>
val configJson = Json.fromJsonObject(jsEncoder.encodeObject(config))
Json.fromFields(List((N, Json.fromString(Platform.Js.name)), (C, configJson)))
case Platform.Native(config) =>
val configJson = Json.fromJsonObject(nativeEncoder.encodeObject(config))
Json.fromFields(List((N, Json.fromString(Platform.Native.name)), (C, configJson)))
case Platform.Jvm(config, mainClass) =>
val configJson = jvmEncoder(config)
val mainClassJson = implicitly[RootEncoder[Option[String]]].apply(mainClass)
Json.fromFields(
List((N, Json.fromString(Platform.Jvm.name)), (C, configJson), (M, mainClassJson)))
case Platform.Js(config, mainClass) =>
val configJson = jsEncoder(config)
val mainClassJson = implicitly[RootEncoder[Option[String]]].apply(mainClass)
Json.fromFields(
List((N, Json.fromString(Platform.Js.name)), (C, configJson), (M, mainClassJson)))
case Platform.Native(config, mainClass) =>
val configJson = nativeEncoder(config)
val mainClassJson = implicitly[RootEncoder[Option[String]]].apply(mainClass)
Json.fromFields(
List((N, Json.fromString(Platform.Native.name)), (C, configJson), (M, mainClassJson)))
}
}

implicit val platformDecoder: Decoder[Platform] = new Decoder[Platform] {
private final val C = "config"
override def apply(c: HCursor): Result[Platform] = {
c.downField(N).as[String].flatMap {
case Platform.Jvm.name => c.downField(C).as[JvmConfig].map(c => Platform.Jvm(c))
case Platform.Js.name => c.downField(C).as[JsConfig].map(c => Platform.Js(c))
case Platform.Native.name => c.downField(C).as[NativeConfig].map(c => Platform.Native(c))
case Platform.Jvm.name =>
for {
config <- c.get[JvmConfig](C)
mainClass <- c.get[List[String]](M)
} yield Platform.Jvm(config, mainClass.headOption)
case Platform.Js.name =>
for {
config <- c.get[JsConfig](C)
mainClass <- c.get[List[String]](M)
} yield Platform.Js(config, mainClass.headOption)
case Platform.Native.name =>
for {
config <- c.get[NativeConfig](C)
mainClass <- c.get[List[String]](M)
} yield Platform.Native(config, mainClass.headOption)
case _ =>
val msg = s"Expected platform ${Platform.All.map(s => s"'$s'").mkString(", ")})."
val msg = s"Expected platform ${Platform.All.map(s => s"'$s'").mkString(", ")})"
Left(DecodingFailure(msg, c.history))
}
}
Expand Down
52 changes: 36 additions & 16 deletions config/src/main/scala-2.12/bloop/config/ConfigEncoderDecoders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ object ConfigEncoderDecoders {
case JavaThenScala.id => Right(JavaThenScala)
case ScalaThenJava.id => Right(ScalaThenJava)
case _ =>
val msg = s"Expected compile order ${CompileOrder.All.map(s => s"'$s'").mkString(", ")})."
val msg = s"Expected compile order ${CompileOrder.All.map(s => s"'$s'").mkString(", ")})"
Left(DecodingFailure(msg, List(CursorOp.DownField("id"))))
}
}
Expand All @@ -51,7 +51,7 @@ object ConfigEncoderDecoders {
case Debug.id => Right(Debug)
case Release.id => Right(Release)
case _ =>
val msg = s"Expected linker mode ${LinkerMode.All.map(s => s"'$s'").mkString(", ")})."
val msg = s"Expected linker mode ${LinkerMode.All.map(s => s"'$s'").mkString(", ")})"
Left(DecodingFailure(msg, c.history))
}
}
Expand All @@ -70,7 +70,7 @@ object ConfigEncoderDecoders {
case ModuleKindJS.NoModule.id => Right(ModuleKindJS.NoModule)
case ModuleKindJS.CommonJSModule.id => Right(ModuleKindJS.CommonJSModule)
case _ =>
val msg = s"Expected module kind ${ModuleKindJS.All.map(s => s"'$s'").mkString(", ")})."
val msg = s"Expected module kind ${ModuleKindJS.All.map(s => s"'$s'").mkString(", ")})"
Left(DecodingFailure(msg, c.history))
}
}
Expand All @@ -90,29 +90,49 @@ object ConfigEncoderDecoders {

private final val N = "name"
private final val C = "config"
private final val M = "mainClass"

implicit val platformEncoder: RootEncoder[Platform] = new RootEncoder[Platform] {
override final def apply(platform: Platform): Json = platform match {
case Platform.Jvm(config) =>
val configJson = Json.fromJsonObject(jvmEncoder.encodeObject(config))
Json.fromFields(List((N, Json.fromString(Platform.Jvm.name)), (C, configJson)))
case Platform.Js(config) =>
val configJson = Json.fromJsonObject(jsEncoder.encodeObject(config))
Json.fromFields(List((N, Json.fromString(Platform.Js.name)), (C, configJson)))
case Platform.Native(config) =>
val configJson = Json.fromJsonObject(nativeEncoder.encodeObject(config))
Json.fromFields(List((N, Json.fromString(Platform.Native.name)), (C, configJson)))
case Platform.Jvm(config, mainClass) =>
val configJson = jvmEncoder(config)
val mainClassJson = implicitly[RootEncoder[Option[String]]].apply(mainClass)
Json.fromFields(List(
(N, Json.fromString(Platform.Jvm.name)),
(C, configJson),
(M, mainClassJson)))
case Platform.Js(config, mainClass) =>
val configJson = jsEncoder(config)
val mainClassJson = implicitly[RootEncoder[Option[String]]].apply(mainClass)
Json.fromFields(List(
(N, Json.fromString(Platform.Js.name)),
(C, configJson),
(M, mainClassJson)))
case Platform.Native(config, mainClass) =>
val configJson = nativeEncoder(config)
val mainClassJson = implicitly[RootEncoder[Option[String]]].apply(mainClass)
Json.fromFields(List(
(N, Json.fromString(Platform.Native.name)),
(C, configJson),
(M, mainClassJson)))
}
}

implicit val platformDecoder: Decoder[Platform] = new Decoder[Platform] {
private final val C = "config"
override def apply(c: HCursor): Result[Platform] = {
c.downField(N).as[String].flatMap {
case Platform.Jvm.name => c.downField(C).as[JvmConfig].map(c => Platform.Jvm(c))
case Platform.Js.name => c.downField(C).as[JsConfig].map(c => Platform.Js(c))
case Platform.Native.name => c.downField(C).as[NativeConfig].map(c => Platform.Native(c))
case Platform.Jvm.name => c.get[JvmConfig](C).flatMap(config =>
c.get[List[String]](M).map(mainClass =>
Platform.Jvm(config, mainClass.headOption)))
case Platform.Js.name => c.get[JsConfig](C).flatMap(config =>
c.get[List[String]](M).map(mainClass =>
Platform.Js(config, mainClass.headOption)))
case Platform.Native.name => c.get[NativeConfig](C).flatMap(config =>
c.get[List[String]](M).map(mainClass =>
Platform.Native(config, mainClass.headOption)))
case _ =>
val msg = s"Expected platform ${Platform.All.map(s => s"'$s'").mkString(", ")})."
val msg = s"Expected platform ${Platform.All.map(s => s"'$s'").mkString(", ")})"
Left(DecodingFailure(msg, c.history))
}
}
Expand Down
11 changes: 6 additions & 5 deletions config/src/main/scala/bloop/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,24 @@ object Config {
sealed abstract class Platform(val name: String) {
type Config <: PlatformConfig
def config: Config
def mainClass: Option[String]
}

object Platform {
private[bloop] val default: Platform = Jvm(JvmConfig.empty)
private[bloop] val default: Platform = Jvm(JvmConfig.empty, None)

object Js { val name: String = "js" }
case class Js(override val config: JsConfig) extends Platform(Js.name) {
case class Js(override val config: JsConfig, override val mainClass: Option[String]) extends Platform(Js.name) {
type Config = JsConfig
}

object Jvm { val name: String = "jvm" }
case class Jvm(override val config: JvmConfig) extends Platform(Jvm.name) {
case class Jvm(override val config: JvmConfig, override val mainClass: Option[String]) extends Platform(Jvm.name) {
type Config = JvmConfig
}

object Native { val name: String = "native" }
case class Native(override val config: NativeConfig) extends Platform(Native.name) {
case class Native(override val config: NativeConfig, override val mainClass: Option[String]) extends Platform(Native.name) {
type Config = NativeConfig
}

Expand Down Expand Up @@ -242,7 +243,7 @@ object Config {
val classesDir = Files.createTempFile("classes", "test")
classesDir.toFile.deleteOnExit()

val platform = Platform.Jvm(JvmConfig(Some(Paths.get("/usr/lib/jvm/java-8-jdk")), Nil))
val platform = Platform.Jvm(JvmConfig(Some(Paths.get("/usr/lib/jvm/java-8-jdk")), Nil), Some("module.Main"))
val project = Project(
"dummy-project",
workingDirectory,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main/scala/bloop/Project.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ object Project {
}

val javaEnv = project.platform match {
case Config.Platform.Jvm(Config.JvmConfig(home, jvmOptions)) =>
case Config.Platform.Jvm(Config.JvmConfig(home, jvmOptions), _) =>
val jvmHome = home.map(AbsolutePath.apply).getOrElse(JavaEnv.DefaultJavaHome)
JavaEnv(jvmHome, jvmOptions.toArray)
case _ => JavaEnv.default
Expand Down
57 changes: 40 additions & 17 deletions frontend/src/main/scala/bloop/engine/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ object Interpreter {

private def missingScalaNativeToolchain(state: State, project: Project): Task[State] = {
val artifactName = project.platform match {
case Config.Platform.Native(config) => ScalaNativeToolchain.artifactNameFrom(config.version)
case Config.Platform.Native(config, _) => ScalaNativeToolchain.artifactNameFrom(config.version)
case _ => sys.error(s"Fatal error: Scala Native project ${project.name} does not have a JavaScript configuration. Report upstream.")
}

Expand All @@ -295,7 +295,7 @@ object Interpreter {

private def missingScalaJsToolchain(state: State, project: Project): Task[State] = {
val artifactName = project.platform match {
case Config.Platform.Js(config) => ScalaJsToolchain.artifactNameFrom(config.version)
case Config.Platform.Js(config, _) => ScalaJsToolchain.artifactNameFrom(config.version)
case _ => sys.error(s"Fatal error: Scala.js project ${project.name} does not have a JavaScript configuration. Report upstream.")
}

Expand All @@ -308,7 +308,7 @@ object Interpreter {

def doJsRun(project: Project, config0: Config.JsConfig)(state: State): Task[State] = {
compileAnd(state, project, reporter, false, sequential, "`link`") { state =>
cmd.main.orElse(getMainClass(state, project)) match {
getMainClass(state, project, cmd.main) match {
case None => Task.now(state)
case Some(main) =>
project.jsToolchain match {
Expand Down Expand Up @@ -337,7 +337,7 @@ object Interpreter {

def doNativeRun(project: Project, config0: Config.NativeConfig)(state: State): Task[State] = {
compileAnd(state, project, reporter, false, sequential, "`link`") { state =>
cmd.main.orElse(getMainClass(state, project)) match {
getMainClass(state, project, cmd.main) match {
case None => Task.now(state)
case Some(main) =>
project.nativeToolchain match {
Expand Down Expand Up @@ -370,16 +370,16 @@ object Interpreter {

case Some(project) =>
project.platform match {
case Platform.Native(config) =>
case Platform.Native(config, _) =>
if (cmd.watch) watch(project, state, doNativeRun(project, config))
else doNativeRun(project, config)(state)

case Platform.Js(config) =>
case Platform.Js(config, _) =>
if (cmd.watch) watch(project, state, doJsRun(project, config))
else doJsRun(project, config)(state)

case Platform.Jvm(_) =>
val msg = s"Cannot link JVM project ${project.name}. `link` is only available for Scala Native and Scala.js projects"
case Platform.Jvm(_, _) =>
val msg = s"Cannot link JVM project ${project.name}. `link` is only available for Scala Native and Scala.js projects."
state.withError(msg, ExitStatus.InvalidCommandLineOption)
}
}
Expand All @@ -405,14 +405,14 @@ object Interpreter {
case Some(project) =>
def doRun(state: State): Task[State] = {
compileAnd(state, project, reporter, false, sequential, "`run`") { state =>
cmd.main.orElse(getMainClass(state, project)) match {
getMainClass(state, project, cmd.main) match {
case None => Task.now(state.mergeStatus(ExitStatus.RunError))
case Some(mainClass) =>
val args = cmd.args.toArray
val cwd = cmd.cliOptions.common.workingPath

project.platform match {
case Platform.Native(config0) =>
case Platform.Native(config0, _) =>
project.nativeToolchain match {
case Some(toolchain) =>
config0.output.flatMap(Tasks.reasonOfInvalidPath(_)) match {
Expand All @@ -426,7 +426,7 @@ object Interpreter {
}
case None => missingScalaNativeToolchain(state, project)
}
case Platform.Js(config0) =>
case Platform.Js(config0, _) =>
project.jsToolchain match {
case Some(toolchain) =>
config0.output.flatMap(Tasks.reasonOfInvalidPath(_, ".js")) match {
Expand Down Expand Up @@ -462,19 +462,42 @@ object Interpreter {
state.mergeStatus(ExitStatus.InvalidCommandLineOption)
}

private def getMainClass(state: State, project: Project): Option[String] = {
/** @param userMainClass User-supplied main class */
private def getMainClass(state: State, project: Project, userMainClass: Option[String]): Option[String] = {
Tasks.findMainClasses(state, project) match {
case Array() =>
state.logger.error(s"No main classes were found in the project '${project.name}'")
None
case Array(main) =>
Some(main)
case mainClasses =>
val eol = System.lineSeparator
val message = s"""Several main classes were found. Specify one of:
|${mainClasses.mkString(" * ", s"$eol * ", "")}""".stripMargin
state.logger.error(message)
None
def listClasses() = {
val eol = System.lineSeparator
val message =
s"""Available main classes:
|${mainClasses.mkString(" * ", s"$eol * ", "")}""".stripMargin
state.logger.error(message)
None
}

val configMainClass = project.platform.mainClass

if (userMainClass.isDefined) {
if (mainClasses.contains(userMainClass.get)) userMainClass
else {
state.logger.error(s"Provided main class $userMainClass was not found in project '${project.name}'")
listClasses()
}
} else if (configMainClass.isDefined) {
if (mainClasses.contains(configMainClass.get)) configMainClass
else {
state.logger.error(s"Default main class ${configMainClass.get} was not found in project '${project.name}'")
listClasses()
}
} else {
state.logger.error(s"No main classes were configured for project '${project.name}'")
listClasses()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ abstract class ToolchainCompanion[Toolchain] {
*/
def resolveToolchain(platform: Config.Platform, logger: Logger): Toolchain = {
val (artifactName, toolchain) = platform match {
case Config.Platform.Js(config) => (artifactNameFrom(config.version), config.toolchain)
case Config.Platform.Native(config) => (artifactNameFrom(config.version), config.toolchain)
case Config.Platform.Jvm(_) =>
case Config.Platform.Js(config, _) => (artifactNameFrom(config.version), config.toolchain)
case Config.Platform.Native(config, _) => (artifactNameFrom(config.version), config.toolchain)
case Config.Platform.Jvm(_, _) =>
throw new IllegalArgumentException("Fatal programming error: JVM toolchain does not exist.")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ object MojoImplementation {
val `scala` = Config.Scala(mojo.getScalaOrganization(), mojo.getScalaArtifactID(),
mojo.getScalaVersion(), scalacArgs, allScalaJars)
val javaHome = Some(abs(mojo.getJavaHome().getParentFile.getParentFile))
val platform = Config.Platform.Jvm(Config.JvmConfig(javaHome, launcher.getJvmArgs().toList))
val mainClass = if (launcher.getMainClass().isEmpty) None else Some(launcher.getMainClass())
val platform = Config.Platform.Jvm(Config.JvmConfig(javaHome, launcher.getJvmArgs().toList), mainClass)
val resolution = Config.Resolution.empty
val project = Config.Project(name, baseDirectory, sourceDirs, dependencyNames, classpath, out, analysisOut, classesDir, `scala`, java, sbt, test, platform, compileSetup, resolution)
Config.File(Config.File.LatestVersion, project)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ object Bloop extends ExternalModule {
Config.JvmConfig(
home = T.ctx().env.get("JAVA_HOME").map(s => Path(s).toNIO),
options = module.forkArgs().toList
)
), mainClass = module.mainClass()
)
}

Expand Down
Loading

0 comments on commit 926926b

Please sign in to comment.