Skip to content

Commit

Permalink
typelead#23 Etlas Configuration.
Browse files Browse the repository at this point in the history
  • Loading branch information
Romastyi committed Apr 22, 2019
1 parent 97af18a commit 863ce8f
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 76 deletions.
4 changes: 3 additions & 1 deletion example/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ lazy val root = (project in file(".")).
version := "0.1.0-SNAPSHOT"
)),
name := "example",
etaVersion := "0.8.6b5",
etaVersion in Eta := "0.8.6b5",
etlasUseLocal in Eta := false,
etlasVersion in Eta := "1.5.0.0",
exposedModules in EtaLib += "Example.Transform",
libraryDependencies in EtaLib ++= Seq(
eta("aeson"),
Expand Down
147 changes: 91 additions & 56 deletions src/main/scala/com/typelead/Etlas.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,67 +6,105 @@ import java.lang.{ProcessBuilder => JProcessBuilder}
import EtaDependency.EtaVersion
import sbt.Keys._
import sbt._
import sbt.io.Using

import scala.collection.mutable.ArrayBuffer
import scala.sys.process.{Process, ProcessLogger}
import scala.util.Try
import scala.util.{Properties, Try}

final case class Etlas(workDir: File, dist: File, etaVersion: EtaVersion) {
final case class Etlas(installPath: Option[File], workDir: File, dist: File, etaVersion: EtaVersion) {

import Etlas._

def changeWorkDir(workDir: File): Etlas = this.copy(workDir = workDir)

def build(cabal: Cabal, log: Logger): Unit = {
etlas(Seq("build").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
etlas(installPath, Seq("build").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
()
}

def buildArtifacts(cabal: Cabal, log: Logger, filter: Cabal.Artifact.Filter): Unit = {
cabal.getArtifacts(filter).foreach {
artifact => etlas(Seq("build", artifact.depsPackage).withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
artifact => etlas(installPath, Seq("build", artifact.depsPackage).withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
}
}

def clean(log: Logger): Unit = {
etlas(Seq("clean").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log)
etlas(installPath, Seq("clean").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log)
()
}

def deps(cabal: Cabal, log: Logger, filter: Cabal.Artifact.Filter): Seq[String] = {
def filterDepsLog(s: String): Boolean = defaultFilterLog(s) || !(s.startsWith("dependency") || s.startsWith("maven-dependencies"))
cabal.getArtifacts(filter).flatMap { artifact =>
etlas(Seq("deps", artifact.depsPackage, "--keep-going").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, saveOutput = true, filterLog = filterDepsLog)
etlas(installPath, Seq("deps", artifact.depsPackage, "--keep-going").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, saveOutput = true, filterLog = filterDepsLog)
}
}

def install(log: Logger): Unit = {
log.info("Installing dependencies...")
etlas(Seq("install", "--dependencies-only"), workDir, log)
etlas(installPath, Seq("install", "--dependencies-only"), workDir, log)
}

def freeze(log: Logger): Unit = {
etlas(Seq("freeze"), workDir, log)
etlas(installPath, Seq("freeze"), workDir, log)
}

def run(log: Logger): Unit = {
etlas(Seq("run").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log)
etlas(installPath, Seq("run").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log)
()
}

def runArtifacts(cabal: Cabal, log: Logger, filter: Cabal.Artifact.Filter): Unit = {
cabal.getArtifacts(Cabal.Artifact.and(Cabal.Artifact.executable, filter)).foreach { artifact =>
etlas(Seq("run", artifact.name).withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
etlas(installPath, Seq("run", artifact.name).withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
}
}

def test(log: Logger): Unit = {
etlas(Seq("test").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
etlas(installPath, Seq("test").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
}

def testArtifacts(cabal: Cabal, log: Logger, filter: Cabal.Artifact.Filter): Unit = {
cabal.getArtifacts(Cabal.Artifact.and(Cabal.Artifact.testSuite, filter)).foreach { artifact =>
etlas(Seq("test", artifact.name).withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
etlas(installPath, Seq("test", artifact.name).withBuildDir(dist).withEtaVersion(etaVersion), workDir, log, filterLog = _ => true)
}
}

def init(name: String,
description: String,
version: String,
developers: Seq[Developer],
homepage: Option[URL],
sourceDir: File,
log: Logger): Unit = {
log.info("Initialize project...")
etlas(installPath, Seq(
"init",
"--non-interactive",
"--is-executable",
s"--package-dir=${workDir.getCanonicalPath}",
s"--package-name=$name-eta",
s"--synopsis=$description",
s"--version=$version",
s"--source-dir=${IO.relativize(workDir, sourceDir).getOrElse(sourceDir.getCanonicalPath)}",
"--language=Haskell2010"
) ++ developers.headOption.toList.flatMap(
dev => Seq(s"--author=${dev.name}", s"--email=${dev.email}")
) ++ homepage.map(
url => s"--homepage=$url"
), workDir, log)
}

def repl(log: sbt.Logger): Try[Unit] = {
def console0(): Unit = {
log.info("Starting Eta interpreter...")
fork(installPath, Seq("repl").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log)
}
Run.executeTrapExit(console0(), log).recover {
case _: InterruptedException =>
log.info("Eta REPL was interrupted.")
()
}
}

Expand Down Expand Up @@ -112,7 +150,12 @@ object Etlas {
}
}

private def etlas(args: Seq[String],
private def getEtlasBinary(installPath: Option[File]): String = {
installPath.map(_.getCanonicalPath).getOrElse("etlas")
}

private def etlas(installPath: Option[File],
args: Seq[String],
workDir: File,
log: Logger,
saveOutput: Boolean = false,
Expand All @@ -133,8 +176,9 @@ object Etlas {
}

IO.createDirectory(workDir)
logCmd(s"Running `etlas ${args.mkString(" ")} in '$workDir'`...")(log)
val exitCode = synchronized(Process("etlas" +: args, workDir) ! logger)
val binary = getEtlasBinary(installPath)
logCmd(s"Running `$binary ${args.mkString(" ")} in '$workDir'`...")(log)
val exitCode = synchronized(Process(binary +: args, workDir) ! logger)

if (exitCode != 0) {
sys.error("\n\n[etlas] Exit Failure " ++ exitCode.toString)
Expand All @@ -144,11 +188,11 @@ object Etlas {

}

private def fork(args: Seq[String], workDir: File, log: sbt.Logger): Unit = {
private def fork(installPath: Option[File], args: Seq[String], workDir: File, log: sbt.Logger): Unit = {

logCmd(s"Running `etlas ${args.mkString(" ")} in '$workDir'`...")(Logger(log))

val jpb = new JProcessBuilder(("etlas" +: args).toArray: _ *)
val binary = getEtlasBinary(installPath)
logCmd(s"Running `$binary ${args.mkString(" ")} in '$workDir'`...")(Logger(log))
val jpb = new JProcessBuilder((binary +: args).toArray: _ *)
jpb.directory(workDir)
jpb.redirectInput(Redirect.INHERIT)
val exitCode = Process(jpb).run(SbtUtils.terminalIO).exitValue()
Expand All @@ -164,49 +208,40 @@ object Etlas {
def withEtaVersion(etaVersion: EtaVersion): Seq[String] = s"--select-eta=${etaVersion.friendlyVersion}" +: args
}

def etaVersion(workDir: File, log: Logger): EtaVersion = {
EtaVersion(etlas(Seq("exec", "eta", "--", "--numeric-version"), workDir, log, saveOutput = true).head)
def etaVersion(installPath: Option[File], workDir: File, log: Logger): EtaVersion = {
EtaVersion(etlas(installPath, Seq("exec", "eta", "--", "--numeric-version"), workDir, log, saveOutput = true).head)
}

def etlasVersion(workDir: File, log: Logger): String = {
etlas(Seq("--numeric-version"), workDir, log, saveOutput = true).head
def etlasVersion(installPath: Option[File], workDir: File, log: Logger): String = {
etlas(None, Seq("--numeric-version"), workDir, log, saveOutput = true).head
}

def init(workDir: File,
name: String,
description: String,
version: String,
developers: Seq[Developer],
homepage: Option[URL],
sourceDir: File,
log: Logger): Unit = {
log.info("Initialize project...")
etlas(Seq(
"init",
"--non-interactive",
"--is-executable",
s"--package-dir=${workDir.getCanonicalPath}",
s"--package-name=$name-eta",
s"--synopsis=$description",
s"--version=$version",
s"--source-dir=${IO.relativize(workDir, sourceDir).getOrElse(sourceDir.getCanonicalPath)}",
"--language=Haskell2010"
) ++ developers.headOption.toList.flatMap(
dev => Seq(s"--author=${dev.name}", s"--email=${dev.email}")
) ++ homepage.map(
url => s"--homepage=$url"
), workDir, log)
}
private[typelead] val DEFAULT_ETLAS_REPO = "http://cdnverify.eta-lang.org/eta-binaries"

def repl(workDir: File, dist: File, etaVersion: EtaVersion, log: sbt.Logger): Try[Unit] = {
def console0(): Unit = {
log.info("Starting Eta interpreter...")
fork(Seq("repl").withBuildDir(dist).withEtaVersion(etaVersion), workDir, log)
def download(repo: String, dest: File, version: String, log: Logger): Unit = {
val (arch, ext) = if (Properties.isWin)
("x86_64-windows", ".exe")
else if (Properties.isMac)
("x86_64-osx", "")
else
("x86_64-linux", "")
val binary = "etlas" + ext
if (dest.exists()) {
()
} else {
val url = new URL(repo + "/etlas-" + version + "/binaries/" + arch + "/" + binary)
log.info(s"Downloading Etlas binary from '$url' to '${dest.getCanonicalPath}' ...")
IO.createDirectory(dest.getParentFile)
Using.urlInputStream(url) { input =>
IO.transfer(input, dest)
}
}
Run.executeTrapExit(console0(), log).recover {
case _: InterruptedException =>
log.info("Eta REPL was interrupted.")
()
if (dest.setExecutable(true)) {
val version = etlas(Some(dest), Seq("--version"), dest.getParentFile, log, saveOutput = true).head
if (version.toLowerCase.contains("etlas")) ()
else sys.error(s"Executable '${dest.getCanonicalPath}' is not Etlas binary.")
} else {
sys.error("Could not set permissions for Eltas binary.")
}
}

Expand Down
48 changes: 29 additions & 19 deletions src/main/scala/com/typelead/SbtEta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ object SbtEta extends AutoPlugin {
lazy val EtaExe: Configuration = config("EtaExe")
lazy val EtaTest: Configuration = config("EtaTest")

lazy val etaVersion = settingKey[String]("Version of the Eta compiler.")
lazy val etlasVersion = settingKey[String]("Version of the Etlas build tool.")
lazy val etaCompile = taskKey[Unit]("Build your Eta project.")
lazy val etaVersion = settingKey[String]("Version of the Eta compiler.")
lazy val etlasVersion = settingKey[String]("Version of the Etlas build tool.")
lazy val etlasUseLocal = settingKey[Boolean]("If `true`, use instance of Etlas installed in your system. If `false`, use Etlas specified by project settings.")
lazy val etlasPath = settingKey[File]("Specifies the path to Etlas executable used in this build.")
lazy val etlasRepository = settingKey[String]("URL address of Etlas repository. Do not change!")
lazy val etaCompile = taskKey[Unit]("Build your Eta project.")

// Eta configuration DSL

Expand Down Expand Up @@ -60,23 +63,36 @@ object SbtEta extends AutoPlugin {

private lazy val etlas = settingKey[Etlas]("Helper for Etlas commands.")
private lazy val etaCabal = taskKey[Cabal]("Structure of the .cabal file.")
private lazy val etaPackage = taskKey[EtaPackage]("")
private lazy val etaPackage = taskKey[EtaPackage]("Sturcture of Eta package.")

private lazy val baseEtaSettings: Seq[Def.Setting[_]] = {
inConfig(Eta)(Seq(
baseDirectory := (target in Compile).value / "eta",
target := (target in Compile).value / "eta" / "dist",
// Plugin specific tasks
etlas := Etlas(baseDirectory.value, target.value, EtaVersion(etaVersion.value)),
etaCabal := refreshCabalTask.value,
etaPackage := {
etlas.value.getEtaPackage(etaCabal.value, Logger(streams.value))
etlasUseLocal := true,
etlasPath := BuildPaths.defaultGlobalBase / "etlas" / "etlas",
etlasRepository := Etlas.DEFAULT_ETLAS_REPO,
etlas := {
if (etlasUseLocal.value) {
Etlas(None, baseDirectory.value, target.value, EtaVersion(etaVersion.value))
} else {
val installPath = etlasPath.value
Etlas.download(etlasRepository.value, installPath, etlasVersion.value, Logger(sLog.value))
Etlas(Some(installPath), baseDirectory.value, target.value, EtaVersion(etaVersion.value))
}
},
etlasVersion := {
val installPath = if (etlasUseLocal.value) None else Some(etlasPath.value)
Etlas.etlasVersion(installPath, baseDirectory.value, Logger(sLog.value))
},
etaVersion := {
Etlas.etaVersion(baseDirectory.value, Logger(sLog.value)).friendlyVersion
val installPath = if (etlasUseLocal.value) None else Some(etlasPath.value)
Etlas.etaVersion(installPath, baseDirectory.value, Logger(sLog.value)).friendlyVersion
},
etlasVersion := {
Etlas.etlasVersion(baseDirectory.value, Logger(sLog.value))
etaCabal := refreshCabalTask.value,
etaPackage := {
etlas.value.getEtaPackage(etaCabal.value, Logger(streams.value))
},
// Standard tasks
clean := {
Expand Down Expand Up @@ -352,8 +368,7 @@ object SbtEta extends AutoPlugin {
log.warn(s"Found '$file' in '${workDir.getCanonicalPath}'. Could not initialize new Eta project.")
state
case None =>
Etlas.init(
workDir,
extracted.get(etlas in Eta).init(
extracted.get(normalizedName),
extracted.get(description),
EtaDependency.getPackageVersion(extracted.get(version)),
Expand All @@ -368,12 +383,7 @@ object SbtEta extends AutoPlugin {

private def etaReplCommand: Command = Command.command("eta-repl") { state =>
val extracted = Project.extract(state)
Etlas.repl(
extracted.get(baseDirectory in Eta),
extracted.get(target in Eta),
EtaVersion(extracted.get(etaVersion in Eta)),
extracted.get(sLog)
).get
extracted.get(etlas in Eta).repl(extracted.get(sLog)).get
println()
state
}
Expand Down

0 comments on commit 863ce8f

Please sign in to comment.