Merge pull request #38 from bazelbuild/flag_refactor

Flag refactor
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/Args.kt b/kotlin/builder/src/io/bazel/kotlin/builder/Args.kt
new file mode 100644
index 0000000..84f4973
--- /dev/null
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/Args.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2018 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.bazel.kotlin.builder
+
+import com.squareup.moshi.KotlinJsonAdapterFactory
+import com.squareup.moshi.Moshi
+
+typealias ArgMap = Map<String, List<String>>
+
+/**
+ * Get the mandatory single value from a key
+ */
+fun ArgMap.mandatorySingle(key: String): String =
+        optionalSingle(key) ?: throw IllegalArgumentException("$key is not optional")
+
+fun ArgMap.optionalSingle(key: String): String? =
+        optional(key)?.let {
+            when (it.size) {
+                0 -> throw IllegalArgumentException("$key did not have a value")
+                1 -> it[0]
+                else -> throw IllegalArgumentException("$key should have a single value")
+            }
+        }
+
+fun ArgMap.mandatory(key: String): Array<String> = optional(key) ?: throw IllegalArgumentException("$key is not optional")
+fun ArgMap.optional(key: String): Array<String>? = this[key]?.toTypedArray()
+
+inline fun <reified T : Any> ArgMap.mandatoryFromJson(key: String): T = optionalFromJson(key) ?: throw IllegalArgumentException("$key is not optional")
+inline fun <reified T : Any> ArgMap.optionalFromJson(key: String): T? = optionalSingle(key)?.let { moshi.adapter(T::class.java).fromJson(it)!! }
+
+@PublishedApi
+internal val moshi = Moshi.Builder().let {
+    it.add(KotlinJsonAdapterFactory())
+    it.build()
+}
+
+/**
+ * Test if a flag is set
+ */
+fun ArgMap.flag(key: String): Boolean = this[key]?.let { true } ?: false
+
+object ArgMaps {
+    fun from(args: List<String>): ArgMap = mutableMapOf<String, MutableList<String>>().also { argsToMap(args, it) }
+
+    fun argsToMap(args: List<String>, argMap: MutableMap<String, MutableList<String>>,  isFlag: (String) -> Boolean = { it.startsWith("--") }) {
+        var currentKey: String = args.first().also { require(isFlag(it)) { "first arg must be a flag" } }
+        val currentValue = mutableListOf<String>()
+        val mergeCurrent = {
+            argMap.computeIfAbsent(currentKey, { mutableListOf() }).addAll(currentValue)
+            currentValue.clear()
+        }
+        args.drop(1).forEach {
+            if (it.startsWith("--")) {
+                mergeCurrent()
+                currentKey = it
+            } else {
+                currentValue.add(it)
+            }
+        }.also { mergeCurrent() }
+    }
+}
\ No newline at end of file
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/CommandLineProgram.kt b/kotlin/builder/src/io/bazel/kotlin/builder/CommandLineProgram.kt
index 082d0fe..bfe532a 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/CommandLineProgram.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/CommandLineProgram.kt
@@ -15,6 +15,8 @@
  */
 package io.bazel.kotlin.builder
 
+import io.bazel.kotlin.builder.model.Flags
+
 /**
  * Interface for command line programs.
  *
@@ -33,26 +35,9 @@
     fun apply(args: List<String>): Int
 
     abstract class Base(
-            protected val flags: FlagNameMap = emptyMap()
-    ): CommandLineProgram {
-        private val mandatoryFlags: List<Flag> = flags.values.filter { it is Flag.Mandatory }
-
+    ) : CommandLineProgram {
         private fun createContext(toolchain: KotlinToolchain, args: List<String>): Context {
-            val tally = mutableMapOf<Flag, String>()
-
-            if (args.size % 2 != 0) {
-                throw RuntimeException("args should be k,v pairs")
-            }
-
-            for (i in 0 until args.size / 2) {
-                val flag = args[i * 2]
-                val value = args[i * 2 + 1]
-                val field = flags[flag] ?: throw RuntimeException("unrecognised arg: " + flag)
-                tally[field] = value
-            }
-
-            mandatoryFlags.forEach { require(tally.containsKey(it)) { "missing mandatory flag ${it.globalFlag}" } }
-            return Context(toolchain, tally)
+            return Context(toolchain, Flags(ArgMaps.from(args)))
         }
 
         abstract val toolchain: KotlinToolchain
@@ -61,7 +46,7 @@
         override fun apply(args: List<String>): Int {
             val ctx = createContext(toolchain, args)
             var exitCode = 0
-            for (action in actions(toolchain,ctx)) {
+            for (action in actions(toolchain, ctx)) {
                 exitCode = action(ctx)
                 if (exitCode != 0)
                     break
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/Context.kt b/kotlin/builder/src/io/bazel/kotlin/builder/Context.kt
index 9dbc1c7..1e04e69 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/Context.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/Context.kt
@@ -17,21 +17,20 @@
 
 package io.bazel.kotlin.builder
 
+import io.bazel.kotlin.builder.model.Flags
 import java.util.stream.Stream
 
 class Context internal constructor(
         val toolchain: KotlinToolchain,
-        private val flags: Map<Flag, String>
+        val flags: Flags
+
 ) {
     private val meta = mutableMapOf<Meta<*>, Any>()
 
-    fun copyOfFlags(vararg fields: Flag): Map<Flag, String> = fields.mapNotNull { f -> flags[f]?.let { f to it } }.toMap()
-
     fun apply(vararg consumers: (Context) -> Unit) {
         Stream.of(*consumers).forEach { it(this) }
     }
 
-    internal operator fun get(flag: Flag): String? = flags[flag]
     operator fun <T : Any> get(key: Meta<T>): T? = meta[key] as T?
     internal fun <T : Any> putIfAbsent(key: Meta<T>, value: T): T? = meta.putIfAbsent(key, value as Any) as T?
 }
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/Flag.kt b/kotlin/builder/src/io/bazel/kotlin/builder/Flag.kt
deleted file mode 100644
index 416ffcc..0000000
--- a/kotlin/builder/src/io/bazel/kotlin/builder/Flag.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2018 The Bazel Authors. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.bazel.kotlin.builder
-
-import com.squareup.moshi.JsonAdapter
-import com.squareup.moshi.KotlinJsonAdapterFactory
-import com.squareup.moshi.Moshi
-import kotlin.reflect.KClass
-import kotlin.reflect.full.declaredMemberProperties
-
-sealed class Flag(val globalFlag: String, val kotlinFlag: String? = null) {
-    open operator fun get(context: Context): String? = context[this]
-
-    inline fun <reified T : Any> renderJsonAndBind(override: JsonAdapter<T>? = null): Meta<T> = object : Meta<T> {
-        val adapter = override ?: moshi.adapter(T::class.java)
-        override val id: String = "flag $globalFlag"
-        override val defaultValue: T? = null
-        override fun get(ctx: Context): T? = super.get(ctx) ?: this@Flag[ctx]?.let { adapter.fromJson(it) }
-    }
-
-    class Optional(globalFlag: String, kotlinFlag: String? = null) : Flag(globalFlag, kotlinFlag)
-
-    class Mandatory(globalFlag: String, kotlinFlag: String? = null) : Flag(globalFlag, kotlinFlag) {
-        override fun get(context: Context): String = requireNotNull(super.get(context)) { "mandatory flag $globalFlag not present" }
-    }
-
-    companion object {
-        @PublishedApi
-        internal val moshi = Moshi.Builder().let {
-            it.add(KotlinJsonAdapterFactory())
-            it.build()
-        }
-    }
-}
-
-/**
- * all of the static flag properties declared in a class.
- */
-// works for objects only.
-private fun <T : Any> KClass<T>.allFlags(): Sequence<Flag> {
-    val obj = requireNotNull(this.objectInstance) { "only collects flag instances from classes with an object instance" }
-    return declaredMemberProperties.asSequence().mapNotNull {
-        it.get(obj).takeIf(Flag::class::isInstance).let { it as Flag }
-    }
-}
-
-typealias FlagNameMap = Map<String, Flag>
-
-/**
- * Map from flag name to flag collected from the static properteis declared in a class.
- */
-fun <T : Any> KClass<T>.flagsByName(): FlagNameMap = allFlags().associateBy { it.globalFlag }
\ No newline at end of file
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/KotlinBuilder.kt b/kotlin/builder/src/io/bazel/kotlin/builder/KotlinBuilder.kt
index 244686b..606fdbc 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/KotlinBuilder.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/KotlinBuilder.kt
@@ -17,15 +17,12 @@
 
 
 import io.bazel.kotlin.builder.mode.jvm.actions.*
-import io.bazel.kotlin.builder.model.Flags
 import java.io.IOException
 
 /**
  * Bazel Kotlin Compiler worker.
  */
-object KotlinBuilder : CommandLineProgram.Base(
-        flags = Flags::class.flagsByName()
-) {
+object KotlinBuilder : CommandLineProgram.Base() {
     override val toolchain: KotlinToolchain = try {
         KotlinToolchain()
     } catch (e: IOException) {
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/CreateOutputJar.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/CreateOutputJar.kt
index 7075633..b65f9e6 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/CreateOutputJar.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/CreateOutputJar.kt
@@ -19,8 +19,6 @@
 import io.bazel.kotlin.builder.Context
 import io.bazel.kotlin.builder.KotlinToolchain
 import io.bazel.kotlin.builder.model.CompileDirectories
-import io.bazel.kotlin.builder.model.Flags
-import io.bazel.kotlin.builder.model.PluginDescriptors
 import io.bazel.kotlin.builder.utils.executeAndAwaitSuccess
 import java.nio.file.Path
 
@@ -31,7 +29,7 @@
     private fun MutableList<String>.addAllFrom(dir: Path) = addAll(arrayOf("-C", dir.toString(), "."))
 
     private fun MutableList<String>.maybeAddAnnotationProcessingGeneratedClasses(ctx: Context) {
-        PluginDescriptors[ctx]?.let { pluginDescriptor ->
+        ctx.flags.plugins?.let { pluginDescriptor ->
             CompileDirectories[ctx].annotionProcessingClasses.takeIf {
                 pluginDescriptor.processors.isNotEmpty() && it.toFile().exists()
             }?.also { this.addAllFrom(it) }
@@ -42,7 +40,7 @@
         try {
             mutableListOf(
                     toolchain.JAR_TOOL_PATH,
-                    "cf", Flags.OUTPUT_CLASSJAR[ctx]
+                    "cf", ctx.flags.outputClassJar
             ).also { args ->
                 args.addAllFrom(CompileDirectories[ctx].classes)
                 args.maybeAddAnnotationProcessingGeneratedClasses(ctx)
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/GenerateJdepsFile.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/GenerateJdepsFile.kt
index 6229d20..07c851c 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/GenerateJdepsFile.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/GenerateJdepsFile.kt
@@ -20,10 +20,6 @@
 import io.bazel.kotlin.builder.Context
 import io.bazel.kotlin.builder.KotlinToolchain
 import io.bazel.kotlin.builder.mode.jvm.utils.JdepsParser
-import io.bazel.kotlin.builder.model.Flags.CLASSPATH
-import io.bazel.kotlin.builder.model.Flags.LABEL
-import io.bazel.kotlin.builder.model.Flags.OUTPUT_CLASSJAR
-import io.bazel.kotlin.builder.model.Flags.OUTPUT_JDEPS
 import io.bazel.kotlin.builder.utils.executeAndWaitOutput
 import io.bazel.kotlin.builder.utils.rootCause
 import java.io.FileOutputStream
@@ -35,23 +31,22 @@
     private val isKotlinImplicit = JdepsParser.pathSuffixMatchingPredicate(toolchain.KOTLIN_LIB_DIR, *toolchain.KOTLIN_STD_LIBS)
 
     override fun invoke(ctx: Context): Int {
-        val classJar = OUTPUT_CLASSJAR[ctx]
-        val classPath = CLASSPATH[ctx]
-        val output = OUTPUT_JDEPS[ctx]
-        val label = LABEL[ctx]
-
         val jdepsContent: Deps.Dependencies
 
         try {
-            jdepsContent = executeAndWaitOutput(10, toolchain.JDEPS_PATH, "-cp", classPath, classJar).let {
-                JdepsParser.parse(label, classJar, classPath, it.stream(), isKotlinImplicit)
+            jdepsContent = executeAndWaitOutput(10, toolchain.JDEPS_PATH, "-cp", ctx.flags.classpath, ctx.flags.outputClassJar).let {
+                JdepsParser.parse(
+                        ctx.flags.label,
+                        ctx.flags.outputClassJar,
+                        ctx.flags.classpath,
+                        it.stream(), isKotlinImplicit)
             }
         } catch (e: Exception) {
             throw RuntimeException("error reading or parsing jdeps file", e.rootCause)
         }
 
         try {
-            Paths.get(output).also {
+            Paths.get(ctx.flags.outputJdeps).also {
                 Files.deleteIfExists(it)
                 FileOutputStream(Files.createFile(it).toFile()).use {
                     jdepsContent.writeTo(it)
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/Initialize.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/Initialize.kt
index 8811fa2..526c333 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/Initialize.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/Initialize.kt
@@ -19,8 +19,10 @@
 import io.bazel.kotlin.builder.BuildAction
 import io.bazel.kotlin.builder.Context
 import io.bazel.kotlin.builder.KotlinToolchain
+import io.bazel.kotlin.builder.model.CompileDirectories
+import io.bazel.kotlin.builder.model.CompilePluginConfig
+import io.bazel.kotlin.builder.model.Metas
 import io.bazel.kotlin.builder.utils.PluginArgs
-import io.bazel.kotlin.builder.model.*
 import java.nio.file.Files
 import java.nio.file.Paths
 
@@ -39,7 +41,7 @@
     }
 
     private fun bindPluginStatus(ctx: Context) {
-        CompilePluginConfig[ctx] = PluginDescriptors[ctx]?.let {
+        CompilePluginConfig[ctx] = ctx.flags.plugins?.let {
             PluginArgs.from(ctx)?.let {
                 CompilePluginConfig(hasAnnotationProcessors = true, args = it.toTypedArray())
             }
@@ -49,7 +51,7 @@
     private fun bindSources(ctx: Context) {
         val javaSources = mutableListOf<String>()
         val allSources = mutableListOf<String>()
-        for (src in requireNotNull(Flags.SOURCES[ctx]).split(":")) {
+        for (src in requireNotNull(ctx.flags.source).split(":")) {
             when {
                 src.endsWith(".java") -> {
                     javaSources.add(src)
@@ -64,7 +66,7 @@
     }
 
     private fun initializeAndBindBindDirectories(ctx: Context) {
-        Files.createDirectories(Paths.get(Flags.COMPILER_OUTPUT_BASE[ctx])).let {
+        Files.createDirectories(Paths.get(ctx.flags.compilerOutputBase)).let {
             CompileDirectories[ctx] = CompileDirectories(it)
         }
     }
@@ -73,9 +75,8 @@
      * parses the label, sets up the meta elements and returns the target part.
      */
     private fun bindLabelComponents(ctx: Context) {
-        val label = requireNotNull(Flags.LABEL[ctx])
-        val parts = label.split(":")
-        require(parts.size == 2) { "the label $label is invalid" }
+        val parts = ctx.flags.label.split(":")
+        require(parts.size == 2) { "the label ${ctx.flags.label} is invalid" }
         Metas.PKG[ctx] = parts[0]
         Metas.TARGET[ctx] = parts[1]
     }
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JavaMainCompile.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JavaMainCompile.kt
index f5483b6..4393818 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JavaMainCompile.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/JavaMainCompile.kt
@@ -19,10 +19,9 @@
 import io.bazel.kotlin.builder.CompileResult
 import io.bazel.kotlin.builder.Context
 import io.bazel.kotlin.builder.KotlinToolchain
-import io.bazel.kotlin.builder.utils.annotationProcessingGeneratedJavaSources
 import io.bazel.kotlin.builder.model.CompileDirectories
-import io.bazel.kotlin.builder.model.Flags
 import io.bazel.kotlin.builder.model.Metas
+import io.bazel.kotlin.builder.utils.annotationProcessingGeneratedJavaSources
 import io.bazel.kotlin.builder.utils.executeAndAwait
 
 /**
@@ -35,14 +34,14 @@
 
     override fun invoke(ctx: Context): Int {
         val javaSources = Metas.JAVA_SOURCES.mustGet(ctx)
-        val classpath = Flags.CLASSPATH[ctx]
+
         val additionalJavaSources = ctx.annotationProcessingGeneratedJavaSources()?.toList() ?: emptyList()
 
         if (javaSources.isNotEmpty() || additionalJavaSources.isNotEmpty()) {
             val classesDirectory = CompileDirectories[ctx].classes
             val incrementalData = CompileDirectories[ctx].annotationProcessingIncrementalData
 
-            val args = mutableListOf(toolchain.JAVAC_PATH, "-cp", "$classesDirectory/:$incrementalData/:$classpath", "-d", classesDirectory.toString()).also {
+            val args = mutableListOf(toolchain.JAVAC_PATH, "-cp", "$classesDirectory/:$incrementalData/:${ctx.flags.classpath}", "-d", classesDirectory.toString()).also {
                 // Kotlin takes care of annotation processing.
                 it.add("-proc:none")
                 it.addAll(javaSources)
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/KotlinMainCompile.kt b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/KotlinMainCompile.kt
index 76e2b22..5f0be66 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/KotlinMainCompile.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/mode/jvm/actions/KotlinMainCompile.kt
@@ -23,7 +23,6 @@
 import io.bazel.kotlin.builder.mode.jvm.utils.KotlinCompilerOutputProcessor
 import io.bazel.kotlin.builder.model.CompileDirectories
 import io.bazel.kotlin.builder.model.CompilePluginConfig
-import io.bazel.kotlin.builder.model.Flags
 import io.bazel.kotlin.builder.model.Metas
 import io.bazel.kotlin.builder.utils.addAll
 import io.bazel.kotlin.builder.utils.annotationProcessingGeneratedJavaSources
@@ -33,15 +32,6 @@
 // invocations Configured via Kotlin use eager analysis in some corner cases this can result in classpath exceptions from the Java Compiler..
 class KotlinMainCompile(toolchain: KotlinToolchain) : BuildAction("compile kotlin classes", toolchain) {
     companion object {
-        /**
-         * Default fields that are directly mappable to kotlin compiler args.
-         */
-        private val COMPILE_MAPPED_FLAGS = arrayOf(
-                Flags.CLASSPATH,
-                Flags.KOTLIN_API_VERSION,
-                Flags.KOTLIN_LANGUAGE_VERSION,
-                Flags.KOTLIN_JVM_TARGET)
-
         val Result = CompileResult.Meta("kotlin_compile_result")
     }
 
@@ -54,15 +44,18 @@
         val args = mutableListOf<String>()
         val compileDirectories = CompileDirectories[ctx]
 
-        ctx.copyOfFlags(*COMPILE_MAPPED_FLAGS).forEach { field, arg ->
-            args.add(field.kotlinFlag!!); args.add(arg)
-        }
+        args.addAll(
+                "-cp", ctx.flags.classpath,
+                "-api-version", ctx.flags.kotlinApiVersion,
+                "-language-version", ctx.flags.kotlinLanguageVersion,
+                "-jvm-target", ctx.flags.kotlinJvmTarget
+        )
 
         args
                 .addAll("-module-name", ctx.moduleName)
                 .addAll("-d", compileDirectories.classes.toString())
 
-        Flags.KOTLIN_PASSTHROUGH_FLAGS[ctx]?.takeIf { it.isNotBlank() }?.also { args.addAll(it.split(" ")) }
+        ctx.flags.kotlinPassthroughFlags?.takeIf { it.isNotBlank() }?.also { args.addAll(it.split(" ")) }
 
         return args
     }
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/model/Flags.kt b/kotlin/builder/src/io/bazel/kotlin/builder/model/Flags.kt
index 209be6c..a11283e 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/model/Flags.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/model/Flags.kt
@@ -15,31 +15,29 @@
  */
 package io.bazel.kotlin.builder.model
 
-import io.bazel.kotlin.builder.Flag
+import io.bazel.kotlin.builder.*
 
 /**
  * The flags supported by the worker.
  */
-object Flags {
-    val LABEL = Flag.Mandatory(JavaBuilderFlags.TARGET_LABEL.flag)
-    val OUTPUT_CLASSJAR = Flag.Mandatory(JavaBuilderFlags.OUTPUT.flag)
-    val SOURCES = Flag.Mandatory(JavaBuilderFlags.SOURCES.flag)
-    val CLASSPATH = Flag.Mandatory(JavaBuilderFlags.CLASSPATH.flag, "-cp")
+class Flags(argMap: ArgMap) {
+    val label = argMap.mandatorySingle(JavaBuilderFlags.TARGET_LABEL.flag)
+    val outputClassJar = argMap.mandatorySingle(JavaBuilderFlags.OUTPUT.flag)
+    val source = argMap.mandatorySingle(JavaBuilderFlags.SOURCES.flag)
+    val classpath = argMap.mandatorySingle(JavaBuilderFlags.CLASSPATH.flag)
+    val plugins = argMap.optionalFromJson<PluginDescriptors>("--kt-plugins")
+    val outputJdeps = argMap.mandatorySingle("--output_jdeps")
 
-    val PLUGINS = Flag.Optional("--kt-plugins")
-
-    val OUTPUT_JDEPS = Flag.Mandatory("--output_jdeps")
-    val COMPILER_OUTPUT_BASE = Flag.Mandatory("--compiler_output_base")
-
-    val KOTLIN_API_VERSION = Flag.Optional("--kotlin_api_version", "-api-version")
-    val KOTLIN_LANGUAGE_VERSION = Flag.Optional("--kotlin_language_version", "-language-version")
-    val KOTLIN_JVM_TARGET = Flag.Optional("--kotlin_jvm_target", "-jvm-target")
+    val compilerOutputBase = argMap.mandatorySingle("--compiler_output_base")
+    val kotlinApiVersion = argMap.mandatorySingle("--kotlin_api_version")
+    val kotlinLanguageVersion = argMap.mandatorySingle("--kotlin_language_version")
+    val kotlinJvmTarget = argMap.mandatorySingle("--kotlin_jvm_target")
 
     /**
      * These flags are passed through to the compiler verbatim, the rules ensure they are safe. These flags are to toggle features or they carry a single value
      * so the string is tokenized by space.
      */
-    val KOTLIN_PASSTHROUGH_FLAGS = Flag.Optional("--kotlin_passthrough_flags")
+    val kotlinPassthroughFlags = argMap.optionalSingle("--kotlin_passthrough_flags")
 
-    val KOTLIN_MODULE_NAME = Flag.Optional("--kotlin_module_name")
+    val kotlinModuleName = argMap.optionalSingle("--kotlin_module_name")
 }
\ No newline at end of file
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/model/PluginDescriptors.kt b/kotlin/builder/src/io/bazel/kotlin/builder/model/PluginDescriptors.kt
index 9b2b66c..96a3323 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/model/PluginDescriptors.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/model/PluginDescriptors.kt
@@ -16,7 +16,6 @@
 package io.bazel.kotlin.builder.model
 
 import com.squareup.moshi.Json
-import io.bazel.kotlin.builder.Meta
 
 data class AnnotationProcessor(
         @Json(name = "processor_class") val processorClass: String,
@@ -33,6 +32,4 @@
          * The list of Annotation processors.
          */
         val processors: List<AnnotationProcessor>
-) {
-    companion object : Meta<PluginDescriptors> by Flags.PLUGINS.renderJsonAndBind()
-}
+)
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/utils/MiscUtils.kt b/kotlin/builder/src/io/bazel/kotlin/builder/utils/MiscUtils.kt
index 9ed9b85..1aab958 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/utils/MiscUtils.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/utils/MiscUtils.kt
@@ -17,12 +17,11 @@
 package io.bazel.kotlin.builder.utils
 
 import io.bazel.kotlin.builder.Context
-import io.bazel.kotlin.builder.model.Flags
 import io.bazel.kotlin.builder.model.Metas
 
-fun <T, C: MutableCollection<T>> C.addAll(vararg entries: T): C = this.also { addAll(entries) }
+fun <T, C : MutableCollection<T>> C.addAll(vararg entries: T): C = this.also { addAll(entries) }
 
 fun String?.supplyIfNullOrBlank(s: () -> String): String = this?.takeIf { it.isNotBlank() } ?: s()
 
 val Context.moduleName: String
-    get() = Flags.KOTLIN_MODULE_NAME[this].supplyIfNullOrBlank { "${Metas.PKG[this].trimStart { it == '/' }.replace('/', '_')}-${Metas.TARGET[this]}" }
+    get() = flags.kotlinModuleName.supplyIfNullOrBlank { "${Metas.PKG[this].trimStart { it == '/' }.replace('/', '_')}-${Metas.TARGET[this]}" }
diff --git a/kotlin/builder/src/io/bazel/kotlin/builder/utils/PluginUtils.kt b/kotlin/builder/src/io/bazel/kotlin/builder/utils/PluginUtils.kt
index 27f4094..dfe1689 100644
--- a/kotlin/builder/src/io/bazel/kotlin/builder/utils/PluginUtils.kt
+++ b/kotlin/builder/src/io/bazel/kotlin/builder/utils/PluginUtils.kt
@@ -19,7 +19,6 @@
 import io.bazel.kotlin.builder.KotlinToolchain.CompilerPlugin
 import io.bazel.kotlin.builder.model.CompileDirectories
 import io.bazel.kotlin.builder.model.CompilePluginConfig
-import io.bazel.kotlin.builder.model.PluginDescriptors
 import java.io.ByteArrayOutputStream
 import java.io.ObjectOutputStream
 import java.util.*
@@ -63,7 +62,7 @@
 
     companion object {
         fun from(ctx: Context): List<String>? =
-                PluginDescriptors[ctx]?.let { descriptor ->
+                ctx.flags.plugins?.let { descriptor ->
                     if (descriptor.processors.isNotEmpty()) {
                         val compileDirectories = CompileDirectories[ctx]
 
diff --git a/kotlin/internal/rules.bzl b/kotlin/internal/rules.bzl
index 93047ab..70ef7c8 100644
--- a/kotlin/internal/rules.bzl
+++ b/kotlin/internal/rules.bzl
@@ -85,7 +85,7 @@
     utils.actions.write_launcher(
         ctx,
         transitive_runtime_jars,
-        main_class = "com.google.testing.junit.runner.BazelTestRunner",
+        main_class = ctx.attr.main_class,
         jvm_flags = launcherJvmFlags + ctx.attr.jvm_flags,
     )
     return compile.make_providers(
diff --git a/kotlin/kotlin.bzl b/kotlin/kotlin.bzl
index 346db07..e1f564a 100644
--- a/kotlin/kotlin.bzl
+++ b/kotlin/kotlin.bzl
@@ -320,7 +320,7 @@
             allow_files = True,
         ),
         "test_class": attr.string(),
-        #      "main_class": attr.string(),
+        "main_class": attr.string(default="com.google.testing.junit.runner.BazelTestRunner"),
     }.items()),
     executable = True,
     outputs = _binary_outputs,