Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Advanced techniques for building Kotlin DSL(s) - Android Worldwide

Advanced techniques for building Kotlin DSL(s) - Android Worldwide

How to create Kotlin DSL that looks like the syntax of a programming language?
In this talk, I would like to talk about advanced techniques for creating Kotlin DSL.
As an example, we will see how to build a DSL that looks like a Starlark programming language, a Python dialect used for writing build scripts for Bazel build system. This DSL is a declarative code generator that builds an abstract syntax tree of a Starlark language and then generates a formatted code based on it.
However, all the concepts described in this talk are applicable to any kind of Kotlin DSL(s) including ones for Android projects.
The example project was presented at BazelCon 2021, a Google conference about Bazel build system: https://youtu.be/dz-CFEwJuko
In addition, there is a blog post that shows the capabilities of this DSL: https://proandroiddev.com/304fa8b3680c
The source code is available on GitHub: https://github.com/Morfly/airin

Pavlo Stavytskyi

July 27, 2022
Tweet

More Decks by Pavlo Stavytskyi

Other Decks in Programming

Transcript

  1. Concrete DSL example • Starlark code generator (Python dialect) •

    Replicate Starlark syntax in Kotlin • https://github.com/Morfly/airin → airin-starlark 9
  2. Concrete DSL example • Starlark code generator (Python dialect) •

    Replicate Starlark syntax in Kotlin • https://github.com/Morfly/airin → airin-starlark • BazelCon 2021: https://youtu.be/dz-CFEwJuko 10
  3. // Starlark/Python SRC_FILES = ["MyClass.java"] android_binary( name = "app", srcs

    = ["MainActivity.java"] + SRCS_FILES, manifest = "AndroidManifest.xml", ) Starlark code generator 12
  4. // Kotlin DSL val starlarkFile = BUILD.bazel { val SRC_FILES

    by list["MyClass.java"] android_binary( name = "app", srcs = list["MainActivity.java"] `+` SRCS_FILES, manifest = "AndroidManifest.xml", ) } fileWriter.write(starlarkFile) // Creates BUILD.bazel file Starlark code generator 13
  5. // Kotlin DSL val starlarkFile = BUILD.bazel { val SRC_FILES

    by list["MyClass.java"] android_binary( name = "app", srcs = list["MainActivity.java"] `+` SRCS_FILES, manifest = "AndroidManifest.xml", ) } fileWriter.write(starlarkFile) // Creates BUILD.bazel file Starlark code generator 14
  6. Starlark code generator android_binary function call Starlark file name argument

    srcs argument manifest argument + binary expression MainActivity.java string literal SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … 15
  7. // Kotlin DSL val SRC_FILES by list["MyClass.java"] android_binary( name =

    "app", srcs = list["MainActivity.java"] `+` SRCS_FILES, manifest = "AndroidManifest.xml", ) Starlark code generator 16 android_binary function call Starlark file name argument srcs argument manifest argument + binary expression MainActivity.java string literal SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES …
  8. // Kotlin DSL val SRC_FILES by list["MyClass.java"] android_binary( name =

    "app", srcs = list["MainActivity.java"] `+` SRCS_FILES, manifest = "AndroidManifest.xml", ) Starlark code generator 17 android_binary function call Starlark file name argument srcs argument manifest argument + binary expression MainActivity.java string literal SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … android_binary function call
  9. // Kotlin DSL val SRC_FILES by list["MyClass.java"] android_binary( name =

    "app", srcs = list["MainActivity.java"] `+` SRCS_FILES, manifest = "AndroidManifest.xml", ) Starlark code generator 18 android_binary function call Starlark file name argument srcs argument manifest argument + binary expression MainActivity.java string literal SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … name argument app string literal
  10. // Kotlin DSL val SRC_FILES by list["MyClass.java"] android_binary( name =

    "app", srcs = list["MainActivity.java"] `+` SRCS_FILES, manifest = "AndroidManifest.xml", ) Starlark code generator 19 android_binary function call Starlark file name argument srcs argument manifest argument + binary expression MainActivity.java string literal SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … manifest argument AndroidManifest.xml string literal
  11. // Kotlin DSL val SRC_FILES by list["MyClass.java"] android_binary( name =

    "app", srcs = list["MainActivity.java"] `+` SRCS_FILES, manifest = "AndroidManifest.xml", ) Starlark code generator 20 android_binary function call Starlark file name argument srcs argument manifest argument + binary expression MainActivity.java string literal SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … srcs argument
  12. // Kotlin DSL val SRC_FILES by list["MyClass.java"] android_binary( name =

    "app", srcs = list["MainActivity.java"] `+` SRCS_FILES, manifest = "AndroidManifest.xml", ) Starlark code generator 21 android_binary function call Starlark file name argument srcs argument manifest argument + binary expression MainActivity.java string literal SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … + binary expression
  13. // Kotlin DSL val SRC_FILES by list["MyClass.java"] android_binary( name =

    "app", srcs = list["MainActivity.java"] `+` SRCS_FILES, manifest = "AndroidManifest.xml", ) Starlark code generator 22 android_binary function call Starlark file name argument srcs argument manifest argument + binary expression MainActivity.java string literal SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … MainActivity.java string literal SRC_FILES reference
  14. class StarlarkFile(val statements: List<Node>): Node class FunctionCall(val name: String, val

    arguments: List<Node>): Node class Argument(val name: String, param: Node) Starlark code generator 25
  15. class StarlarkFile(val statements: List<Node>): Node class FunctionCall(val name: String, val

    arguments: List<Node>): Node class Argument(val name: String, param: Node) class StringLiteral(val value: String): Node class ListExpression(val items: List<Node>): Node class ListReference(val name: String): Node class BinaryPlus(val left: Node, val right: Node): Node Starlark code generator 26
  16. // Kotlin DSL val starlarkFile = BUILD.bazel { ... }

    fileWriter.write(starlarkFile) // Creates BUILD file Entering DSL context 28
  17. // Kotlin DSL val starlarkFile = BUILD.bazel { ... }

    fileWriter.write(starlarkFile) // Creates BUILD file Entering DSL context 29
  18. // Kotlin DSL val starlarkFile = BUILD.bazel { ... }

    fileWriter.write(starlarkFile) // Creates BUILD file Entering DSL context 30
  19. // Implementation object BUILD inline fun BUILD.bazel(body: StarlarkFileContext.() -> Unit):

    StarlarkFile { val context = StarlarkFileContext() context.body() val nodes = context.nodes return StarlarkFile(path, nodes) } Lambdas with receiver 31
  20. // Implementation object BUILD inline fun BUILD.bazel(body: StarlarkFileContext.() -> Unit):

    StarlarkFile { val context = StarlarkFileContext() context.body() val nodes = context.nodes return StarlarkFile(path, nodes) } Lambdas with receiver 32
  21. // Implementation object BUILD inline fun BUILD.bazel(body: StarlarkFileContext.() -> Unit):

    StarlarkFile { val context = StarlarkFileContext() context.body() val nodes = context.nodes return StarlarkFile(path, nodes) } Lambdas with receiver 33
  22. // Kotlin DSL val buildFile = BUILD.bazel { this: StarlarkFileContext

    -> android_binary( name = "app", srcs = list["MainActivity.java"], manifest = "AndroidManifest.xml", ) } Lambdas with receiver 34
  23. // Kotlin DSL class StarlarkFileContext { fun android_binary(...) {...} }

    val buildFile = BUILD.bazel { this: StarlarkFileContext -> android_binary( name = "app", srcs = list["MainActivity.java"], manifest = "AndroidManifest.xml", ) } Lambdas with receiver 35
  24. // Kotlin DSL class StarlarkFileContext { val statements = mutableListOf<Node>()

    fun android_binary(...) {...} } Lambdas with receiver 36
  25. // Kotlin DSL class StarlarkFileContext { val statements = mutableListOf<Node>()

    fun android_binary(...) {...} } Lambdas with receiver 37
  26. // Starlark/Python android_binary( name = "app", srcs = ["MainActivity.java"], manifest

    = "AndroidManifest.xml", ) // Kotlin DSL android_binary( name = "app", srcs = list["MainActivity.java"], manifest = "AndroidManifest.xml", ) Functions 40
  27. // Implementation fun StarlarkFileContext.android_binary( val name: String, val srcs: List<String?>?

    = null, val manifest: String? = null, ) { val args = listOf( Argument("name", StringLiteral(name)), Argument("srcs", ListExpression(srcs)), Argument("manifest", StringLiteral(manifest)) ) statements += FunctionCall("android_binary", args) } Functions 41
  28. // Implementation fun StarlarkFileContext.android_binary( val name: String, val srcs: List<String?>?

    = null, val manifest: String? = null, ) { val args = listOf( Argument("name", StringLiteral(name)), Argument("srcs", ListExpression(srcs)), Argument("manifest", StringLiteral(manifest)) ) statements += FunctionCall("android_binary", args) } Functions 42
  29. // Implementation object _ListExpressionBuilder val list get() = _ListExpressionBuilder operator

    fun <T> _ListExpressionBuilder.get(vararg args: T): List<T> { return ListExpression(listOf(*args)) } Indexed access operator 47
  30. // Implementation object _ListExpressionBuilder val list get() = _ListExpressionBuilder operator

    fun <T> _ListExpressionBuilder.get(vararg args: T): List<T> { return ListExpression(listOf(*args)) } Indexed access operator 48
  31. // Implementation object _ListExpressionBuilder val list get() = _ListExpressionBuilder operator

    fun <T> _ListExpressionBuilder.get(vararg args: T): List<T> { return ListExpression(listOf(*args)) } // Kotlin DSL list["MainActivity.java"] Indexed access operator 49
  32. // Starlark/Python MY_STRING = "value" MY_LIST = [1, 2, 3]

    MY_DICT = {"key": "value"} Property delegation 51
  33. // Starlark/Python MY_STRING = "value" MY_LIST = [1, 2, 3]

    MY_DICT = {"key": "value"} // Kotlin DSL val MY_STRING by "value" val MY_LIST by list[1, 2, 3] val MY_DICT by {"key" to "value"} Property delegation 52
  34. // Starlark/Python MY_STRING = "value" MY_LIST = [1, 2, 3]

    MY_DICT = {"key": "value"} // Kotlin DSL val MY_STRING by "value" val MY_LIST by list[1, 2, 3] val MY_DICT by {"key" to "value"} Property delegation 53
  35. // Starlark/Python MY_STRING = "value" MY_LIST = [1, 2, 3]

    MY_DICT = {"key": "value"} // Kotlin DSL val MY_STRING by "value" val MY_LIST by list[1, 2, 3] val MY_DICT by {"key" to "value"} Property delegation 54
  36. // Starlark/Python MY_STRING = "value" MY_LIST = [1, 2, 3]

    MY_DICT = {"key": "value"} // Kotlin DSL val MY_STRING by "value" val MY_STRING = "value" val MY_LIST by list[1, 2, 3] val MY_DICT by {"key" to "value"} Property delegation 55
  37. // Starlark/Python MY_STRING = "value" MY_LIST = [1, 2, 3]

    MY_DICT = {"key": "value"} // Kotlin DSL val MY_STRING: StringReference by "value" val MY_LIST: ListReference by list[1, 2, 3] val MY_DICT: DictionaryReference by {"key" to "value"} Property delegation 56
  38. // Implementation class ListReference<out T>(val name: String) : List<T>, Node

    class DictionaryReference<K, V: Any>(val name: String) : Map<K, V>, Node Property delegation 58
  39. // Implementation class ListReference<out T>(val name: String) : List<T>, Node

    class DictionaryReference<K, V: Any>(val name: String) : Map<K, V>, Node class StringReference(val name: String) : String, Node Property delegation 59
  40. // Implementation class ListReference<out T>(val name: String) : List<T>, Node

    class DictionaryReference<K, V: Any>(val name: String) : Map<K, V>, Node class StringReference(val name: String) : CharSequence, Node Property delegation 60
  41. // Kotlin DSL val NAME by "app" val SRCS by

    list["MainActivity.java"] val MANIFEST_VALUES by dict {"minSdkVersion" to "21"} android_binary( name = NAME, // String srcs = SRCS, // List manifest_values = MANIFEST_VALUES // Dictionary ) Property delegation 61
  42. // Implementation fun StarlarkFileContext.android_binary( val name: CharSequence, val srcs: List<CharSequence?>?

    = null, val manifest_values: Map<CharSequence?, CharSequence?>? = null, ) {...} Property delegation 62
  43. Dynamic function args 64 // Starlark/Python android_binary( name = "app",

    srcs = ["MainActivity.java"], some_new_argument = {"key": "value"} )
  44. Dynamic function args 65 // Starlark android_binary( name = "app",

    srcs = ["MainActivity.java"], some_new_argument = {"key": "value"} ) // Kotlin DSL android_binary { name = "app" srcs = list["MainActivity.java"] "some_new_argument" `=` {"key" to "value"} }
  45. Dynamic function args 66 // Starlark android_binary( name = "app",

    srcs = ["MainActivity.java"], some_new_argument = {"key": "value"} ) // Kotlin DSL android_binary { name = "app" srcs = list["MainActivity.java"] "some_new_argument" `=` {"key" to "value"} }
  46. Dynamic function args 67 // Starlark android_binary( name = "app",

    srcs = ["MainActivity.java"], some_new_argument = {"key": "value"} ) // Kotlin DSL android_binary { name = "app" srcs = list["MainActivity.java"] "some_new_argument" `=` {"key" to "value"} }
  47. Dynamic function args 68 // Starlark android_binary( name = "app",

    srcs = ["MainActivity.java"], some_new_argument = {"key": "value"} ) // Kotlin DSL android_binary { this: AndroidBinaryContext -> name = "app" srcs = list["MainActivity.java"] "some_new_argument" `=` {"key" to "value"} }
  48. Dynamic function args 69 // Implementation class AndroidBinaryContext { val

    args = linkedSetOf<Argument>() var name: CharSequence by args var srcs: ListType<CharSequence?>? by args var manifest: CharSequence? by args ... }
  49. Dynamic function args 70 // Implementation class AndroidBinaryContext { val

    args = linkedSetOf<Argument>() var name: CharSequence by args var srcs: ListType<CharSequence?>? by args var manifest: CharSequence? by args ... operator fun <V : CharSequence?> Set<Argument>.setValue( thisRef: Any?, property: KProperty<*>, value: V ) { args += Argument(id = property.name, value = Expression(value, ::StringLiteral)) } }
  50. Dynamic function args 71 // Implementation class AndroidBinaryContext { ...

    infix fun CharSequence.`=`(value: CharSequence) { args = Argument(id = this, value = Expression(value, ::StringLiteral)) } }
  51. Dynamic function args 72 // Implementation class AndroidBinaryContext { ...

    infix fun CharSequence.`=`(value: CharSequence) { args = Argument(id = this, value = Expression(value, ::StringLiteral)) } } inline fun <T : Any> Expression(value: T?, builder: (value: T) -> Expression): Expression = when (value) { null -> NoneValue is Expression -> value else -> builder(value) }
  52. Dynamic functions 74 // Starlark/Python some_new_function( name = "app", )

    // Kotlin DSL "some_new_function" { "name" `=` "app" }
  53. Dynamic functions 75 // Implementation operator fun String.invoke(body: FunctionCallContext.() ->

    Unit) { val args = FunctionCallContext().apply(body).args statements += VoidFunctionCall(name, args) }
  54. Dynamic functions 76 // Implementation operator fun String.invoke(body: FunctionCallContext.() ->

    Unit) { val args = FunctionCallContext().apply(body).args statements += VoidFunctionCall(name, args) }
  55. Dynamic functions 77 // Implementation operator fun String.invoke(body: FunctionCallContext.() ->

    Unit) { val args = FunctionCallContext().apply(body).args statements += VoidFunctionCall(name, args) }
  56. Concatenations 80 // Starlark/Python SOURCE_FILES = ["ProfileFragment.java", "ProfileViewModel.java"] android_binary( name

    = "app", srcs = SOURCE_FILES + ["MainActivity.java"], ) // Kotlin DSL val SOURCE_FILES by list["ProfileFragment.java", "ProfileViewModel.java"] android_binary( name = "app", srcs = SOURCE_FILES `+` list["MainActivity.java"], )
  57. Concatenations 81 // Starlark/Python SOURCE_FILES = ["ProfileFragment.java", "ProfileViewModel.java"] android_binary( name

    = "app", srcs = SOURCE_FILES + ["MainActivity.java"], ) // Kotlin DSL val SOURCE_FILES by list["ProfileFragment.java", "ProfileViewModel.java"] android_binary( name = "app", srcs = SOURCE_FILES `+` list["MainActivity.java"], srcs = SOURCE_FILES + list["MainActivity.java"], )
  58. Concatenations 82 // Implementation infix fun <T> List<T>?.`+`(other: List<T>?): List<T>

    = ListBinaryOperation( left = Expression(this, ::ListExpression), operator = BinaryOperator.PLUS, right = Expression(other, ::ListExpression) ) class ListBinaryOperation<T>( val left: Node, val operator: BinaryOperator, val right: Node ) : Node, List<T>
  59. Concatenations 83 // Implementation infix fun <T> List<T>?.`+`(other: List<T>?): List<T>

    = ListBinaryOperation( left = Expression(this, ::ListExpression), operator = BinaryOperator.PLUS, right = Expression(other, ::ListExpression) ) class ListBinaryOperation<T>( val left: Node, val operator: BinaryOperator, val right: Node ) : Node, List<T>
  60. Concatenations 84 // Implementation infix fun <T> List<T>?.`+`(other: List<T>?): List<T>

    = ListBinaryOperation( left = Expression(this, ::ListExpression), operator = BinaryOperator.PLUS, right = Expression(other, ::ListExpression) ) class ListBinaryOperation<T>( val left: Node, val operator: BinaryOperator, val right: Node ) : Node, List<T>
  61. Concatenations 85 // Kotlin DSL val SOURCE_FILES by list["ProfileFragment.java", "ProfileViewModel.java"]

    android_binary { name = "app" srcs = SOURCE_FILES `+` list["MainActivity.java"] } android_binary { name = "app" "srcs" `=` SOURCE_FILES `+` list["MainActivity.java"] }
  62. Concatenations 86 // Kotlin DSL val SOURCE_FILES by list["ProfileFragment.java", "ProfileViewModel.java"]

    android_binary { name = "app" srcs = SOURCE_FILES `+` list["MainActivity.java"] } android_binary { name = "app" "srcs" `=` SOURCE_FILES `+` list["MainActivity.java"] }
  63. Concatenations 87 // Implementation infix fun String.`=`(value: CharSequence): _StringExpressionAccumulator<*> {

    val argument = Argument(id = this, value = Expression(value, ::StringLiteral)) args += argument return _StringExpressionAccumulator(argument) }
  64. Concatenations 88 // Implementation infix fun String.`=`(value: CharSequence): _StringExpressionAccumulator<*> {

    val argument = Argument(id = this, value = Expression(value, ::StringLiteral)) args += argument return _StringExpressionAccumulator(argument) }
  65. Concatenations 89 // Implementation infix fun <E : Node> _StringExpressionAccumulator<E>.`+`(

    other: CharSequence? ):_StringExpressionAccumulator<E> { this.arg.value = BinaryPlusOperator( left = this.arg.value, ..., right = other ) return this }
  66. Concatenations 90 // Implementation infix fun <E : Node> _StringExpressionAccumulator<E>.`+`(

    other: CharSequence? ):_StringExpressionAccumulator<E> { this.arg.value = BinaryPlusOperator( left = this.arg.value, ..., right = other ) return this }
  67. Context conflicts 92 // Kotlin DSL BUILD.bazel { this: StarlarkFileContext

    -> android_binary { this: AndroidBinaryContext -> name = "app" srcs = list["MainActivity.java"] } }
  68. Context conflicts 93 // Kotlin DSL BUILD.bazel { this: StarlarkFileContext

    -> android_binary { this: AndroidBinaryContext -> name = "app" srcs = list["MainActivity.java"] android_binary {...} } }
  69. Context conflicts 95 // Kotlin DSL @DslMarker annotation class LanguageScope

    @LanguageScope class StarlarkFileContext @LanguageScope class AndroidBinaryContext
  70. Context conflicts 96 // Kotlin DSL BUILD.bazel { this: StarlarkFileContext

    -> android_binary { this: AndroidBinaryContext -> name = "app" srcs = list["MainActivity.java"] android_binary {...} } }
  71. Context conflicts 97 // Kotlin DSL BUILD.bazel { this: StarlarkFileContext

    -> android_binary { this: AndroidBinaryContext -> name = "app" srcs = list["MainActivity.java"] `+` list["MainViewModel.java"] } }
  72. Context conflicts 98 // Kotlin DSL BUILD.bazel { this: StarlarkFileContext

    -> android_binary { this: AndroidBinaryContext -> name = "app" srcs = list["MainActivity.java"] `+` list["MainViewModel.java"] } }
  73. Restructuring the DSL 101 // Kotlin DSL sealed interface ConcatenationsFeature

    { // implement `+` operators } @LanguageScope class StarlarkFileContext: ConcatenationsFeature {...} @LanguageScope class AndroidBinaryContext: ConcatenationsFeature {...}
  74. Restructuring the DSL 102 // Kotlin DSL sealed interface ConcatenationsFeature

    { // implement `+` operators } @LanguageScope class StarlarkFileContext: ConcatenationsFeature {...} @LanguageScope class AndroidBinaryContext: ConcatenationsFeature {...}
  75. Restructuring the DSL 103 // Kotlin DSL BUILD.bazel { this:

    StarlarkFileContext -> android_binary { this: AndroidBinaryContext -> name = "app" srcs = list["MainActivity.java"] `+` list["MainViewModel.java"] } }
  76. Restructuring the DSL 104 // Kotlin DSL BUILD.bazel { this:

    StarlarkFileContext -> android_binary { this: AndroidBinaryContext -> name = "app" srcs = list["MainActivity.java"] `+` list["MainViewModel.java"] "manifest_values" `=` { this: DictionaryContext -> "key_" `+` "1" to "value_" `+` "1" } } }
  77. Restructuring the DSL 105 // Kotlin DSL BUILD.bazel { android_binary

    { name = "app" "manifest_values" `=` { this: DictionaryContext -> "key_" `+` "1" to "value_" `+` "1" } } }
  78. List comprehensions 107 // Starlark/Python SRCS = [file + ".java"

    for file in ["MainActivity"]] android_binary( name = "app", srcs = SRCS )
  79. List comprehensions 108 // Starlark/Python SRCS = [file + ".java"

    for file in ["MainActivity"]] android_binary( name = "app", srcs = SRCS ) // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it `+` ".java" } android_binary( name = "app", srcs = SRCS )
  80. List comprehensions 109 // Starlark/Python SRCS = [file + ".java"

    for file in ["MainActivity"]] android_binary( name = "app", srcs = SRCS ) // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it `+` ".java" } android_binary( name = "app", srcs = SRCS )
  81. List comprehensions 110 // Starlark/Python SRCS = [file + ".java"

    for file in ["MainActivity"]] android_binary( name = "app", srcs = SRCS ) // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it `+` ".java" } android_binary( name = "app", srcs = SRCS )
  82. List comprehensions 111 // Starlark/Python SRCS = [file + ".java"

    for file in ["MainActivity"]] android_binary( name = "app", srcs = SRCS ) // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it `+` ".java" } android_binary( name = "app", srcs = SRCS )
  83. List comprehensions 112 // Starlark/Python SRCS = [file + ".java"

    for file in ["MainActivity"]] android_binary( name = "app", srcs = SRCS ) // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it: CharSequence -> it `+` ".java" } android_binary( name = "app", srcs = SRCS )
  84. List comprehensions 113 // Starlark/Python SRCS = [file + ".java"

    for file in ["MainActivity"]] android_binary( name = "app", srcs = SRCS ) // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it: CharSequence -> it `+` ".java" } android_binary( name = "app", srcs = SRCS )
  85. // Kotlin DSL class AndroidBinaryContext : FunctionCallContext() { var name:

    Name by fargs var custom_package: StringType? by fargs var manifest: Label? by fargs var manifest_values: DictionaryType<Key?, Value?>? by fargs var debug_key: Label? by fargs var enable_data_binding: BooleanType? by fargs var multidex: StringType? by fargs var incremental_dexing: NumberType? by fargs var crunch_png: BooleanType? by fargs var dex_shards: NumberType? by fargs var resource_files: ListType<Label?>? by fargs var srcs: ListType<Label?>? by fargs var plugins: ListType<Label?>? by fargs var deps: ListType<Label?>? by fargs var visibility: ListType<Label?>? by fargs var args: ListType<StringType?>? by fargs var env: DictionaryType<Key?, Value?>? by fargs var output_licenses: ListType<StringType?>? by fargs } fun BuildStatementsLibrary.`android_binary`(body: AndroidBinaryContext.() -> Unit): Unit = registerFunctionCallStatement("android_binary", AndroidBinaryContext(), body) Applying KSP 116
  86. // Kotlin DSL class AndroidBinaryContext : FunctionCallContext() { var name:

    Name by fargs var custom_package: StringType? by fargs var manifest: Label? by fargs var manifest_values: DictionaryType<Key?, Value?>? by fargs var debug_key: Label? by fargs var enable_data_binding: BooleanType? by fargs var multidex: StringType? by fargs var incremental_dexing: NumberType? by fargs var crunch_png: BooleanType? by fargs var dex_shards: NumberType? by fargs var resource_files: ListType<Label?>? by fargs var srcs: ListType<Label?>? by fargs var plugins: ListType<Label?>? by fargs var deps: ListType<Label?>? by fargs var visibility: ListType<Label?>? by fargs var args: ListType<StringType?>? by fargs var env: DictionaryType<Key?, Value?>? by fargs var output_licenses: ListType<StringType?>? by fargs } fun BuildStatementsLibrary.`android_binary`(body: AndroidBinaryContext.() -> Unit): Unit = registerFunctionCallStatement("android_binary", AndroidBinaryContext(), body) Applying KSP 117 fun BuildStatementsLibrary.`android_binary`( name: Name, custom_package: StringType? = UnspecifiedString, manifest: Label? = UnspecifiedString, manifest_values: DictionaryType<Key?, Value?>? = UnspecifiedDictionary, debug_key: Label? = UnspecifiedString, enable_data_binding: BooleanType? = UnspecifiedBoolean, multidex: StringType? = UnspecifiedString, incremental_dexing: NumberType? = UnspecifiedNumber, crunch_png: BooleanType? = UnspecifiedBoolean, dex_shards: NumberType? = UnspecifiedNumber, resource_files: ListType<Label?>? = UnspecifiedList, srcs: ListType<Label?>? = UnspecifiedList, plugins: ListType<Label?>? = UnspecifiedList, deps: ListType<Label?>? = UnspecifiedList, visibility: ListType<Label?>? = UnspecifiedList, args: ListType<StringType?>? = UnspecifiedList, env: DictionaryType<Key?, Value?>? = UnspecifiedDictionary, output_licenses: ListType<StringType?>? = UnspecifiedList ): Unit { val _args = linkedSetOf<Argument>().also { it += Argument("name", Expression(name, ::StringLiteral)) if (custom_package !== UnspecifiedString) it += Argument("custom_package", Expression(custom_package, ::StringLiteral)) if (manifest !== UnspecifiedString) it += Argument("manifest", Expression(manifest, ::StringLiteral)) if (manifest_values !== UnspecifiedDictionary) it += Argument("manifest_values", Expression(manifest_values, ::DictionaryExpression)) if (debug_key !== UnspecifiedString) it += Argument("debug_key", Expression(debug_key, ::StringLiteral)) if (enable_data_binding !== UnspecifiedBoolean) it += Argument("enable_data_binding", Expression(enable_data_binding, ::BooleanLiteral)) if (multidex !== UnspecifiedString) it += Argument("multidex", Expression(multidex, ::StringLiteral)) if (incremental_dexing !== UnspecifiedNumber) it += Argument("incremental_dexing", Expression(incremental_dexing, ::NumberLiteral)) if (crunch_png !== UnspecifiedBoolean) it += Argument("crunch_png", Expression(crunch_png, ::BooleanLiteral)) if (dex_shards !== UnspecifiedNumber) it += Argument("dex_shards", Expression(dex_shards, ::NumberLiteral)) if (resource_files !== UnspecifiedList) it += Argument("resource_files", Expression(resource_files, ::ListExpression)) if (srcs !== UnspecifiedList) it += Argument("srcs", Expression(srcs, ::ListExpression)) if (plugins !== UnspecifiedList) it += Argument("plugins", Expression(plugins, ::ListExpression)) if (deps !== UnspecifiedList) it += Argument("deps", Expression(deps, ::ListExpression)) if (visibility !== UnspecifiedList) it += Argument("visibility", Expression(visibility, ::ListExpression)) if (args !== UnspecifiedList) it += Argument("args", Expression(args, ::ListExpression)) if (env !== UnspecifiedDictionary) it += Argument("env", Expression(env, ::DictionaryExpression)) if (output_licenses !== UnspecifiedList) it += Argument("output_licenses", Expression(output_licenses, ::ListExpression)) } return registerFunctionCallStatement("android_binary", _args) }
  87. // Kotlin DSL @LibraryFunction(name = "android_binary",) private interface AndroidBinary {

    @Argument(required = true) val name: Name val manifest: CharSequence? val srcs: List<CharSequence?>? val deps: List<CharSequence?>? ... } Applying KSP 118
  88. So, how do I write a Kotlin Symbol Processor (KSP)?

    https://proandroiddev.com/b9606e9e3818 Applying KSP 119
  89. // Starlark/Python VIEW_MODELS_WITH_RES_IMPORTS = ["MyViewModel.kt", ...] [ genrule( name =

    "modify_imports_in_" + file[0:-3], srcs = [file], outs = [file[0:-3] + "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import org.morfly.airin.sample.app.R/import org.morfly.airin.sample.app.viewmodels.R/g' > $(OUTS) """, ) for file in VIEW_MODELS_WITH_RES_IMPORTS ] Usage example 121
  90. // Starlark/Python VIEW_MODELS_WITH_RES_IMPORTS = ["MyViewModel.kt", ...] [ genrule( name =

    "modify_imports_in_" + file[0:-3], srcs = [file], outs = [file[0:-3] + "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import org.morfly.airin.sample.app.R/import org.morfly.airin.sample.app.viewmodels.R/g' > $(OUTS) """, ) for file in VIEW_MODELS_WITH_RES_IMPORTS ] Usage example 122
  91. // Starlark/Python VIEW_MODELS_WITH_RES_IMPORTS = ["MyViewModel.kt", ...] [ genrule( name =

    "modify_imports_in_" + file[0:-3], srcs = [file], outs = [file[0:-3] + "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import org.morfly.airin.sample.app.R/import org.morfly.airin.sample.app.viewmodels.R/g' > $(OUTS) """, ) for file in VIEW_MODELS_WITH_RES_IMPORTS ] Usage example 123
  92. // Starlark/Python VIEW_MODELS_WITH_RES_IMPORTS = ["MyViewModel.kt", ...] [ genrule( name =

    "modify_imports_in_" + file[0:-3], srcs = [file], outs = [file[0:-3] + "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import org.morfly.airin.sample.app.R/import org.morfly.airin.sample.app.viewmodels.R/g' > $(OUTS) """, ) for file in VIEW_MODELS_WITH_RES_IMPORTS ] Usage example 124
  93. // Starlark/Python VIEW_MODELS_WITH_RES_IMPORTS = ["MyViewModel.kt", ...] [ genrule( name =

    "modify_imports_in_" + file[0:-3], srcs = [file], outs = [file[0:-3] + "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import org.morfly.airin.sample.app.R/import org.morfly.airin.sample.app.viewmodels.R/g' > $(OUTS) """, ) for file in VIEW_MODELS_WITH_RES_IMPORTS ] Usage example 125
  94. // Starlark/Python VIEW_MODELS_WITH_RES_IMPORTS = ["MyViewModel.kt", ...] [ genrule( name =

    "modify_imports_in_" + file[0:-3], srcs = [file], outs = [file[0:-3] + "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import org.morfly.airin.sample.app.R/import org.morfly.airin.sample.app.viewmodels.R/g' > $(OUTS) """, ) for file in VIEW_MODELS_WITH_RES_IMPORTS ] Usage example 126
  95. // Starlark/Python VIEW_MODELS_WITH_RES_IMPORTS = ["MyViewModel.kt", ...] [ genrule( name =

    "modify_imports_in_" + file[0:-3], srcs = [file], outs = [file[0:-3] + "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import org.morfly.airin.sample.app.R/import org.morfly.airin.sample.app.viewmodels.R/g' > $(OUTS) """, ) for file in VIEW_MODELS_WITH_RES_IMPORTS ] Usage example 127
  96. val VIEW_MODELS_WITH_RES_IMPORTS by list["MyViewModel.kt", ...] "file" `in` VIEW_MODELS_WITH_RES_IMPORTS take {

    file -> genrule( name = "modify_imports_in_" `+` file[0..-3], srcs = list[file], outs = list[file[0..-3] `+` "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import $packageName.R/import $packageName.viewmodels.R/g' > $(OUTS) """.trimIndent() ) } Usage example 128
  97. val VIEW_MODELS_WITH_RES_IMPORTS by list["MyViewModel.kt", ...] "file" `in` VIEW_MODELS_WITH_RES_IMPORTS take {

    file -> genrule( name = "modify_imports_in_" `+` file[0..-3], srcs = list[file], outs = list[file[0..-3] `+` "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import $packageName.R/import $packageName.viewmodels.R/g' > $(OUTS) """.trimIndent() ) } Usage example 129
  98. val VIEW_MODELS_WITH_RES_IMPORTS by list["MyViewModel.kt", ...] "file" `in` VIEW_MODELS_WITH_RES_IMPORTS take {

    file -> genrule( name = "modify_imports_in_" `+` file[0..-3], srcs = list[file], outs = list[file[0..-3] `+` "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import $packageName.R/import $packageName.viewmodels.R/g' > $(OUTS) """.trimIndent() ) } Usage example 130
  99. val VIEW_MODELS_WITH_RES_IMPORTS by list["MyViewModel.kt", ...] "file" `in` VIEW_MODELS_WITH_RES_IMPORTS take {

    file -> genrule( name = "modify_imports_in_" `+` file[0..-3], srcs = list[file], outs = list[file[0..-3] `+` "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import $packageName.R/import $packageName.viewmodels.R/g' > $(OUTS) """.trimIndent() ) } Usage example 131
  100. val VIEW_MODELS_WITH_RES_IMPORTS by list["MyViewModel.kt", ...] "file" `in` VIEW_MODELS_WITH_RES_IMPORTS take {

    file -> genrule( name = "modify_imports_in_" `+` file[0..-3], srcs = list[file], outs = list[file[0..-3] `+` "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import $packageName.R/import $packageName.viewmodels.R/g' > $(OUTS) """.trimIndent() ) } Usage example 132
  101. val VIEW_MODELS_WITH_RES_IMPORTS by list["MyViewModel.kt", ...] "file" `in` VIEW_MODELS_WITH_RES_IMPORTS take {

    file -> genrule( name = "modify_imports_in_" `+` file[0..-3], srcs = list[file], outs = list[file[0..-3] `+` "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import $packageName.R/import $packageName.viewmodels.R/g' > $(OUTS) """.trimIndent() ) } Usage example 133
  102. val VIEW_MODELS_WITH_RES_IMPORTS by list["MyViewModel.kt", ...] "file" `in` VIEW_MODELS_WITH_RES_IMPORTS take {

    file -> genrule( name = "modify_imports_in_" `+` file[0..-3], srcs = list[file], outs = list[file[0..-3] `+` "_synthetic.kt"], cmd = """ cat $(SRCS) | sed 's/import $packageName.R/import $packageName.viewmodels.R/g' > $(OUTS) """.trimIndent() ) } Usage example 134
  103. Conclusions 137 • Apply any type of Kotlin syntax elements

    for non-conventional cases • Experiment!