Slide 1

Slide 1 text

Advanced techniques for building Kotlin DSL(s) Pavlo Stavytskyi

Slide 2

Slide 2 text

About me ● Google Developer Expert, Android ● Mobile Infrastructure at Lyft 2

Slide 3

Slide 3 text

Agenda

Slide 4

Slide 4 text

● A concrete example of a Kotlin DSL Agenda 4

Slide 5

Slide 5 text

● A concrete example of a Kotlin DSL ● DSL components Agenda 5

Slide 6

Slide 6 text

Concrete DSL example

Slide 7

Slide 7 text

Concrete DSL example ● Starlark code generator (Python dialect) 7

Slide 8

Slide 8 text

Concrete DSL example ● Starlark code generator (Python dialect) ● Replicate Starlark syntax in Kotlin 8

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Starlark code generator

Slide 11

Slide 11 text

// Starlark/Python SRC_FILES = ["MyClass.java"] android_binary( name = "app", srcs = ["MainActivity.java"] + SRCS_FILES, manifest = "AndroidManifest.xml", ) Starlark code generator 11

Slide 12

Slide 12 text

// 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) Starlark code generator 12

Slide 13

Slide 13 text

// 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

Slide 14

Slide 14 text

Starlark code generator 14 android_binary function call Starlark file name argument srcs argument manifest argument + binary expression list expression SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … MainActivity.java string literal

Slide 15

Slide 15 text

MainActivity.java string literal android_binary function call Starlark file name argument srcs argument manifest argument + binary expression list expression SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … MainActivity.java string literal // 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 15 android_binary function call Starlark file name argument manifest argument + binary expression SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES …

Slide 16

Slide 16 text

Starlark file name argument srcs argument manifest argument + binary expression SRC_FILES reference app string literal AndroidManifest.xml string literal SRC_FILES … MainActivity.java string literal android_binary function call // 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 list expression MainActivity.java string literal

Slide 17

Slide 17 text

// 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 … name argument app string literal list expression MainActivity.java string literal

Slide 18

Slide 18 text

// 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 … manifest argument AndroidManifest.xml string literal list expression MainActivity.java string literal

Slide 19

Slide 19 text

// 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 … srcs argument list expression MainActivity.java string literal

Slide 20

Slide 20 text

// 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 … + binary expression list expression MainActivity.java string literal

Slide 21

Slide 21 text

// 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 … list expression SRC_FILES reference MainActivity.java string literal

Slide 22

Slide 22 text

// 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 … list expression SRC_FILES reference MainActivity.java string literal

Slide 23

Slide 23 text

class StarlarkFile(val statements: List): Node Starlark code generator 23

Slide 24

Slide 24 text

class StarlarkFile(val statements: List): Node class FunctionCall(val name: String, val arguments: List): Node Starlark code generator 24

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

class StarlarkFile(val statements: List): Node class FunctionCall(val name: String, val arguments: List): Node class Argument(val name: String, param: Node) class StringLiteral(val value: String): Node class ListExpression(val items: List): Node class ListReference(val name: String): Node class BinaryPlus(val left: Node, val right: Node): Node Starlark code generator 26

Slide 27

Slide 27 text

Starlark DSL components

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

// Implementation object BUILD Lambdas with receiver 30

Slide 31

Slide 31 text

// 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

Slide 32

Slide 32 text

// 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

Slide 33

Slide 33 text

// 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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

// 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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Function calls

Slide 38

Slide 38 text

// Starlark/Python android_binary( name = "app", srcs = ["MainActivity.java"], manifest = "AndroidManifest.xml", ) Function calls 38

Slide 39

Slide 39 text

// 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", ) Function calls 39

Slide 40

Slide 40 text

// Implementation fun StarlarkFileContext.android_binary( val name: String, val srcs: List? = null, val manifest: String? = null, ) { ... } Function calls 40

Slide 41

Slide 41 text

// Implementation fun StarlarkFileContext.android_binary( val name: String, val srcs: List? = 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) } Function calls 41

Slide 42

Slide 42 text

Indexed access operator

Slide 43

Slide 43 text

// Starlark/Python [1, 2, 3] Indexed access operator 43

Slide 44

Slide 44 text

// Starlark/Python [1, 2, 3] // Kotlin DSL list[1, 2, 3] Indexed access operator 44

Slide 45

Slide 45 text

// Implementation object _ListExpressionBuilder val list get() = _ListExpressionBuilder Indexed access operator 45

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

// Implementation object _ListExpressionBuilder val list get() = _ListExpressionBuilder operator fun _ListExpressionBuilder.get(vararg args: T): List { return ListExpression(listOf(*args)) } // Kotlin DSL list["MainActivity.java"] Indexed access operator 47

Slide 48

Slide 48 text

Property delegation

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

// 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 50

Slide 51

Slide 51 text

// 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 51

Slide 52

Slide 52 text

// 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

Slide 53

Slide 53 text

// 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 53

Slide 54

Slide 54 text

// 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 54

Slide 55

Slide 55 text

// Implementation class ListReference(val name: String) : List, Node Property delegation 55

Slide 56

Slide 56 text

// Implementation class ListReference(val name: String) : List, Node class DictionaryReference(val name: String) : Map, Node Property delegation 56

Slide 57

Slide 57 text

// Implementation class ListReference(val name: String) : List, Node class DictionaryReference(val name: String) : Map, Node class StringReference(val name: String) : String, Node Property delegation 57

Slide 58

Slide 58 text

// Implementation class ListReference(val name: String) : List, Node class DictionaryReference(val name: String) : Map, Node class StringReference(val name: String) : CharSequence, Node Property delegation 58

Slide 59

Slide 59 text

// 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 59

Slide 60

Slide 60 text

// Implementation fun StarlarkFileContext.android_binary( val name: CharSequence, val srcs: List? = null, val manifest_values: Map? = null, ) {...} Property delegation 60

Slide 61

Slide 61 text

Making DSL customizable

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

Dynamic function args 63 // 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"} }

Slide 64

Slide 64 text

Dynamic function args 64 // 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"} }

Slide 65

Slide 65 text

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"} }

Slide 66

Slide 66 text

Dynamic function args 66 // 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"} }

Slide 67

Slide 67 text

Dynamic function args 67 // Implementation class AndroidBinaryContext { val args = linkedSetOf() var name: CharSequence by args var srcs: ListType? by args var manifest: CharSequence? by args }

Slide 68

Slide 68 text

Dynamic function args 68 // Implementation class AndroidBinaryContext { val args = linkedSetOf() var name: CharSequence by args var srcs: ListType? by args var manifest: CharSequence? by args infix fun CharSequence.`=`(value: CharSequence) { args = Argument(id = this, value = Expression(value, ::StringLiteral)) } }

Slide 69

Slide 69 text

Dynamic function args 69 // Implementation class AndroidBinaryContext { val args = linkedSetOf() var name: CharSequence by args var srcs: ListType? by args var manifest: CharSequence? by args infix fun CharSequence.`=`(value: CharSequence) { args = Argument(id = this, value = Expression(value, ::StringLiteral)) } }

Slide 70

Slide 70 text

Dynamic function calls 70 // Starlark/Python some_new_function( name = "app", )

Slide 71

Slide 71 text

Dynamic function calls 71 // Starlark/Python some_new_function( name = "app", ) // Kotlin DSL "some_new_function" { "name" `=` "app" }

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Combining elements

Slide 76

Slide 76 text

Concatenations 76 // Starlark/Python SOURCE_FILES = ["MainViewModel.java"] android_binary( name = "app", srcs = SOURCE_FILES + ["MainActivity.java"], )

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

Concatenations 79 // Implementation infix fun List?.`+`(other: List?): List = ListBinaryOperation( left = Expression(this, ::ListExpression), operator = BinaryOperator.PLUS, right = Expression(other, ::ListExpression) )

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

Context conflicts

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

Context conflicts 85 // Kotlin DSL @DslMarker annotation class LanguageScope

Slide 86

Slide 86 text

Context conflicts 86 // Kotlin DSL @DslMarker annotation class LanguageScope @LanguageScope class StarlarkFileContext @LanguageScope class AndroidBinaryContext

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

Restructuring the DSL

Slide 91

Slide 91 text

Restructuring the DSL 91 // Kotlin DSL sealed interface ConcatenationsFeature { // implement `+` operators }

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

Restructuring the DSL 94 // Kotlin DSL BUILD.bazel { this: StarlarkFileContext -> android_binary { this: AndroidBinaryContext -> "manifest_values" `=` { this: DictionaryContext -> "key_" `+` "1" to "value_" `+` "1" } } }

Slide 95

Slide 95 text

Restructuring the DSL 95 // Kotlin DSL BUILD.bazel { android_binary { "manifest_values" `=` { this: DictionaryContext -> "key_" `+` "1" to "value_" `+` "1" } } }

Slide 96

Slide 96 text

Additional features

Slide 97

Slide 97 text

List comprehensions 97 // Starlark/Python SRCS = [file + ".java" for file in ["MainActivity"]]

Slide 98

Slide 98 text

List comprehensions 98 // Starlark/Python SRCS = [file + ".java" for file in ["MainActivity"]] // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it `+` ".java" }

Slide 99

Slide 99 text

List comprehensions 99 // Starlark/Python SRCS = [file + ".java" for file in ["MainActivity"]] // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it `+` ".java" }

Slide 100

Slide 100 text

List comprehensions 100 // Starlark/Python SRCS = [file + ".java" for file in ["MainActivity"]] // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it `+` ".java" }

Slide 101

Slide 101 text

List comprehensions 101 // Starlark/Python SRCS = [file + ".java" for file in ["MainActivity"]] // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it `+` ".java" }

Slide 102

Slide 102 text

List comprehensions 102 // Starlark/Python SRCS = [file + ".java" for file in ["MainActivity"]] // Kotlin DSL val SRCS by "file" `in` list["MainActivity"] take { it: CharSequence -> it `+` ".java" }

Slide 103

Slide 103 text

Applying KSP

Slide 104

Slide 104 text

// Starlark/Python android_binary( name = "app", srcs = ["MainActivity.java"], manifest = "AndroidManifest.xml", ) Applying KSP 104

Slide 105

Slide 105 text

class AndroidBinaryContext : FunctionCallContext() { var name: Name by fargs var custom_package: StringType? by fargs var manifest: Label? by fargs var manifest_values: DictionaryType? 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? by fargs var srcs: ListType? by fargs var plugins: ListType? by fargs var deps: ListType? by fargs var visibility: ListType? by fargs var args: ListType? by fargs var env: DictionaryType? by fargs var output_licenses: ListType? by fargs } fun BuildStatementsLibrary.`android_binary`(body: AndroidBinaryContext.() -> Unit): Unit = registerFunctionCallStatement("android_binary", AndroidBinaryContext(), body) Applying KSP 105

Slide 106

Slide 106 text

class AndroidBinaryContext : FunctionCallContext() { var name: Name by fargs var custom_package: StringType? by fargs var manifest: Label? by fargs var manifest_values: DictionaryType? 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? by fargs var srcs: ListType? by fargs var plugins: ListType? by fargs var deps: ListType? by fargs var visibility: ListType? by fargs var args: ListType? by fargs var env: DictionaryType? by fargs var output_licenses: ListType? by fargs } fun BuildStatementsLibrary.`android_binary`(body: AndroidBinaryContext.() -> Unit): Unit = registerFunctionCallStatement("android_binary", AndroidBinaryContext(), body) Applying KSP 106 fun BuildStatementsLibrary.`android_binary`( name: Name, custom_package: StringType? = UnspecifiedString, manifest: Label? = UnspecifiedString, manifest_values: DictionaryType? = 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? = UnspecifiedList, srcs: ListType? = UnspecifiedList, plugins: ListType? = UnspecifiedList, deps: ListType? = UnspecifiedList, visibility: ListType? = UnspecifiedList, args: ListType? = UnspecifiedList, env: DictionaryType? = UnspecifiedDictionary, output_licenses: ListType? = UnspecifiedList ): Unit { val _args = linkedSetOf().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) }

Slide 107

Slide 107 text

// Kotlin DSL @LibraryFunction(name = "android_binary") private interface AndroidBinary { @Argument(required = true) val name: Name val manifest: CharSequence? val srcs: List? val deps: List? ... } Applying KSP 107

Slide 108

Slide 108 text

// Kotlin DSL @LibraryFunction(name = "android_binary") private interface AndroidBinary { @Argument(required = true) val name: Name val manifest: CharSequence? val srcs: List? val deps: List? ... } Applying KSP 108

Slide 109

Slide 109 text

How does it look like in practice?

Slide 110

Slide 110 text

// 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 110

Slide 111

Slide 111 text

// 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 111

Slide 112

Slide 112 text

// 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 112

Slide 113

Slide 113 text

// 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 113

Slide 114

Slide 114 text

// 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 114

Slide 115

Slide 115 text

// 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 115

Slide 116

Slide 116 text

// 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 116

Slide 117

Slide 117 text

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 117

Slide 118

Slide 118 text

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 118

Slide 119

Slide 119 text

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 119

Slide 120

Slide 120 text

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 120

Slide 121

Slide 121 text

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 121

Slide 122

Slide 122 text

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 122

Slide 123

Slide 123 text

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 123

Slide 124

Slide 124 text

Conclusions

Slide 125

Slide 125 text

Conclusions 125 ● Apply Kotlin syntax elements in non-conventional ways

Slide 126

Slide 126 text

Conclusions 126 ● Apply Kotlin syntax elements in non-conventional ways ● Experiment!

Slide 127

Slide 127 text

Source code 127 ● https://github.com/Morfly/airin → airin-starlark