Slide 1

Slide 1 text

No content

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

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

Slide 11

Slide 11 text

Starlark code generator

Slide 12

Slide 12 text

// Starlark/Python SRC_FILES = ["MyClass.java"] android_binary( name = "app", srcs = ["MainActivity.java"] + SRCS_FILES, manifest = "AndroidManifest.xml", ) 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

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 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 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 …

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 … android_binary function call

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 … name argument app 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 … manifest argument AndroidManifest.xml 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 … srcs argument

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 … + binary expression

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

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) // Creates BUILD file Entering DSL context 28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

// Kotlin DSL val starlarkFile = BUILD.bazel { ... } fileWriter.write(starlarkFile) // Creates BUILD file Entering DSL context 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

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

Slide 38

Slide 38 text

Functions

Slide 39

Slide 39 text

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

Slide 40

Slide 40 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", ) Functions 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) } Functions 41

Slide 42

Slide 42 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) } Functions 42

Slide 43

Slide 43 text

Indexed access operator

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

// Implementation object _ListExpressionBuilder val list get() = _ListExpressionBuilder 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)) } Indexed access operator 47

Slide 48

Slide 48 text

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

Slide 49

Slide 49 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 49

Slide 50

Slide 50 text

Property delegation

Slide 51

Slide 51 text

// Starlark/Python MY_STRING = "value" MY_LIST = [1, 2, 3] MY_DICT = {"key": "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_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 by "value" val MY_LIST by list[1, 2, 3] val MY_DICT by {"key" to "value"} Property delegation 54

Slide 55

Slide 55 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 55

Slide 56

Slide 56 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 56

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

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

Slide 60

Slide 60 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 60

Slide 61

Slide 61 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 61

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

Making DSL customizable

Slide 64

Slide 64 text

Dynamic function args 64 // Starlark/Python android_binary( name = "app", srcs = ["MainActivity.java"], some_new_argument = {"key": "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 { name = "app" srcs = list["MainActivity.java"] "some_new_argument" `=` {"key" to "value"} }

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

Dynamic function args 72 // Implementation class AndroidBinaryContext { ... infix fun CharSequence.`=`(value: CharSequence) { args = Argument(id = this, value = Expression(value, ::StringLiteral)) } } inline fun Expression(value: T?, builder: (value: T) -> Expression): Expression = when (value) { null -> NoneValue is Expression -> value else -> builder(value) }

Slide 73

Slide 73 text

Dynamic functions 73 // Starlark/Python some_new_function( name = "app", )

Slide 74

Slide 74 text

Dynamic functions 74 // Starlark/Python some_new_function( name = "app", ) // Kotlin DSL "some_new_function" { "name" `=` "app" }

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Combining elements

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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"], )

Slide 81

Slide 81 text

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"], )

Slide 82

Slide 82 text

Concatenations 82 // 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 83

Slide 83 text

Concatenations 83 // 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 84

Slide 84 text

Concatenations 84 // 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 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

Concatenations 89 // Implementation infix fun _StringExpressionAccumulator.`+`( other: CharSequence? ):_StringExpressionAccumulator { this.arg.value = BinaryPlusOperator( left = this.arg.value, ..., right = other ) return this }

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

Context conflicts

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

Restructuring the DSL

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

Additional features

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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 )

Slide 109

Slide 109 text

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 )

Slide 110

Slide 110 text

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 )

Slide 111

Slide 111 text

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 )

Slide 112

Slide 112 text

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 )

Slide 113

Slide 113 text

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 )

Slide 114

Slide 114 text

Applying KSP

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

// 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? 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 117 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 118

Slide 118 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 118

Slide 119

Slide 119 text

So, how do I write a Kotlin Symbol Processor (KSP)? https://proandroiddev.com/b9606e9e3818 Applying KSP 119

Slide 120

Slide 120 text

How does it look like in practice?

Slide 121

Slide 121 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 121

Slide 122

Slide 122 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 122

Slide 123

Slide 123 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 123

Slide 124

Slide 124 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 124

Slide 125

Slide 125 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 125

Slide 126

Slide 126 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 126

Slide 127

Slide 127 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 127

Slide 128

Slide 128 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 128

Slide 129

Slide 129 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 129

Slide 130

Slide 130 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 130

Slide 131

Slide 131 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 131

Slide 132

Slide 132 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 132

Slide 133

Slide 133 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 133

Slide 134

Slide 134 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 134

Slide 135

Slide 135 text

Conclusions

Slide 136

Slide 136 text

Conclusions 136 ● Apply any type of Kotlin syntax elements for non-conventional cases

Slide 137

Slide 137 text

Conclusions 137 ● Apply any type of Kotlin syntax elements for non-conventional cases ● Experiment!

Slide 138

Slide 138 text

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