From a1c7742a57a2c7837b9398b67afca9095d5aec59 Mon Sep 17 00:00:00 2001 From: Chick Markley Date: Mon, 24 Oct 2016 21:00:14 -0700 Subject: [PATCH] Add ExecutionOptionsManager Taken from https://github.com/ucb-bar/firrtl --- build.sbt | 16 +++ .../ExecutionOptionsManager.scala | 126 ++++++++++++++++++ .../ExecutionOptionsManagerSpec.scala | 31 +++++ project/build.properties | 1 + project/dependencies.scala | 16 +++ 5 files changed, 190 insertions(+) create mode 100644 build.sbt create mode 100644 executionoptions/src/main/scala/bar/executionoptions/ExecutionOptionsManager.scala create mode 100644 executionoptions/src/test/scala/bar/executionoptions/ExecutionOptionsManagerSpec.scala create mode 100644 project/build.properties create mode 100644 project/dependencies.scala diff --git a/build.sbt b/build.sbt new file mode 100644 index 00000000..eb35f36c --- /dev/null +++ b/build.sbt @@ -0,0 +1,16 @@ +// See LICENSE for license details. + +import Dependencies._ + +lazy val commonSettings = Seq( + organization := "edu.berkeley.cs", + version := "0.1-SNAPSHOT", + scalaVersion := "2.11.8", + libraryDependencies ++= commonDependencies +) + +lazy val executionoptions = (project in file("executionoptions")) + .settings(commonSettings) + .settings( + libraryDependencies ++= executionoptionsDependencies + ) diff --git a/executionoptions/src/main/scala/bar/executionoptions/ExecutionOptionsManager.scala b/executionoptions/src/main/scala/bar/executionoptions/ExecutionOptionsManager.scala new file mode 100644 index 00000000..05a1e75c --- /dev/null +++ b/executionoptions/src/main/scala/bar/executionoptions/ExecutionOptionsManager.scala @@ -0,0 +1,126 @@ +// See LICENSE for license details. + +package bar.executionoptions + +import scopt.OptionParser + +/** + * Use this trait to define an options class that can add its private command line options to a externally + * declared parser + */ +trait ComposableOptions + +/** + * Most of the chisel toolchain components require a topName which defines a circuit or a device under test. + * Much of the work that is done takes place in a directory. + * It would be simplest to require topName to be defined but in practice it is preferred to defer this. + * For example, in chisel, by deferring this it is possible for the execute there to first elaborate the + * circuit and then set the topName from that if it has not already been set. + */ +case class CommonOptions(topName: String = "", targetDirName: String = "test_run_dir") extends ComposableOptions + +abstract class HasParser(applicationName: String) { + final val parser: OptionParser[Unit] = new OptionParser[Unit](applicationName) {} +} + +trait HasCommonOptions { + self: ExecutionOptionsManager => + var commonOptions = CommonOptions() + + parser.note("common options") + + parser.opt[String]("top-name") + .abbr("tn") + .valueName("") + .foreach { x => + commonOptions = commonOptions.copy(topName = x) + } + .text("This options defines the top level circuit, defaults to dut when possible") + + parser.opt[String]("target-dir") + .abbr("td").valueName("") + .foreach { x => + commonOptions = commonOptions.copy(targetDirName = x) + } + .text(s"This options defines a work directory for intermediate files, default is ${commonOptions.targetDirName}") + + parser.help("help").text("prints this usage text") +} + +/** + * + * @param applicationName The name shown in the usage + */ +class ExecutionOptionsManager(val applicationName: String) extends HasParser(applicationName) with HasCommonOptions { + + def parse(args: Array[String]): Boolean = { + parser.parse(args) + } + + def showUsageAsError(): Unit = parser.showUsageAsError() + + /** + * make sure that all levels of targetDirName exist + * + * @return true if directory exists + */ + def makeTargetDir(): Boolean = { + (new java.io.File(commonOptions.targetDirName)).mkdirs() + } + + def targetDirName: String = commonOptions.targetDirName + + /** + * this function sets the topName in the commonOptions. + * It would be nicer to not need this but many chisel tools cannot determine + * the name of the device under test until other options have been parsed. + * Havin this function allows the code to set the TopName after it has been + * determined + * + * @param newTopName the topName to be used + */ + def setTopName(newTopName: String): Unit = { + commonOptions = commonOptions.copy(topName = newTopName) + } + def setTopNameIfNotSet(newTopName: String): Unit = { + if(commonOptions.topName.isEmpty) { + setTopName(newTopName) + } + } + def topName: String = commonOptions.topName + def setTargetDirName(newTargetDirName: String): Unit = { + commonOptions = commonOptions.copy(targetDirName = newTargetDirName) + } + + /** + * return a file based on targetDir, topName and suffix + * Will not add the suffix if the topName already ends with that suffix + * + * @param suffix suffix to add, removes . if present + * @param fileNameOverride this will override the topName if nonEmpty, when using this targetDir is ignored + * @return + */ + def getBuildFileName(suffix: String, fileNameOverride: String = ""): String = { + makeTargetDir() + + val baseName = if(fileNameOverride.nonEmpty) fileNameOverride else topName + val directoryName = { + if(fileNameOverride.nonEmpty) { + "" + } + else if(baseName.startsWith("./") || baseName.startsWith("/")) { + "" + } + else { + if(targetDirName.endsWith("/")) targetDirName else targetDirName + "/" + } + } + val normalizedSuffix = { + val dottedSuffix = if(suffix.startsWith(".")) suffix else s".$suffix" + if(baseName.endsWith(dottedSuffix)) "" else dottedSuffix + } + + s"$directoryName$baseName$normalizedSuffix" + } +} + diff --git a/executionoptions/src/test/scala/bar/executionoptions/ExecutionOptionsManagerSpec.scala b/executionoptions/src/test/scala/bar/executionoptions/ExecutionOptionsManagerSpec.scala new file mode 100644 index 00000000..f26b1fc5 --- /dev/null +++ b/executionoptions/src/test/scala/bar/executionoptions/ExecutionOptionsManagerSpec.scala @@ -0,0 +1,31 @@ +// See LICENSE for license details. + +package bar.executionoptions + +import org.scalatest.{Matchers, FreeSpec} + +class ExecutionOptionsManagerSpec extends FreeSpec with Matchers { + "ExecutionOptionsManager is a container for one more more ComposableOptions Block" - { + "It has a default CommonOptionsBlock" in { + val manager = new ExecutionOptionsManager("test") + manager.commonOptions.targetDirName should be ("test_run_dir") + } + "But can override defaults like this" in { + val manager = new ExecutionOptionsManager("test") { commonOptions = CommonOptions(topName = "dog") } + manager.commonOptions shouldBe a [CommonOptions] + manager.topName should be ("dog") + manager.commonOptions.topName should be ("dog") + } + "The add method should put a new version of a given type the manager" in { + val manager = new ExecutionOptionsManager("test") { commonOptions = CommonOptions(topName = "dog") } + val initialCommon = manager.commonOptions + initialCommon.topName should be ("dog") + + manager.commonOptions = CommonOptions(topName = "cat") + + val afterCommon = manager.commonOptions + afterCommon.topName should be ("cat") + initialCommon.topName should be ("dog") + } + } +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 00000000..35c88bab --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.12 diff --git a/project/dependencies.scala b/project/dependencies.scala new file mode 100644 index 00000000..e9367cfc --- /dev/null +++ b/project/dependencies.scala @@ -0,0 +1,16 @@ +import sbt._ +import Keys._ + +object Dependencies { + val scalatestVersion = "3.0.0" + val scalatest = "org.scalatest" %% "scalatest" % scalatestVersion % "test" + val scoptVersion = "3.4.0" + val scopt = "com.github.scopt" %% "scopt" % scoptVersion + + val commonDependencies: Seq[ModuleID] = Seq( + scalatest + ) + val executionoptionsDependencies: Seq[ModuleID] = Seq( + scopt + ) +}