Skip to content

Commit 09a1a8b

Browse files
authored
Improve maintenance of the capabilities. (#1252)
* Improve options maintainability * Remove the necessity of updating the capability templates. * Update capability generator to provide defaults and documentation. * Add test to catch changes in the arguments (2.1 broke introspection.) * Fix versions parsing * Fix stardocs and include templates in release * Remove skylib, as it cannot be loaded soon enough in the workspace * Remove bazelproject
1 parent 661142b commit 09a1a8b

File tree

11 files changed

+1118
-231
lines changed

11 files changed

+1118
-231
lines changed

src/main/kotlin/io/bazel/kotlin/generate/BUILD.bazel

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ kt_jvm_import(
1919
kt_jvm_library(
2020
name = "kotlin_release_options_lib",
2121
srcs = glob(["**/*.kt"]),
22+
visibility = [
23+
"//src/test:__subpackages__",
24+
],
2225
deps = [
2326
":kotlinc_jar",
2427
"//kotlin/compiler:kotlin-reflect",
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.bazel.kotlin.generate
22

33
import io.bazel.kotlin.generate.WriteKotlincCapabilities.KotlincCapabilities.Companion.asCapabilities
4-
import io.bazel.kotlin.generate.WriteKotlincCapabilities.KotlincCapability
54
import org.jetbrains.kotlin.cli.common.arguments.Argument
65
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
76
import org.jetbrains.kotlin.config.LanguageVersion
@@ -12,19 +11,75 @@ import java.nio.file.Files
1211
import java.time.Year
1312
import kotlin.io.path.exists
1413
import kotlin.io.path.writeText
14+
import kotlin.jvm.java
15+
import kotlin.math.max
16+
import kotlin.streams.asSequence
17+
import kotlin.streams.toList
1518

1619
/**
1720
* Generates a list of kotlinc flags from the K2JVMCompilerArguments on the classpath.
1821
*/
1922
object WriteKotlincCapabilities {
2023

24+
@JvmStatic
25+
fun main(vararg args: String) {
26+
// TODO: Replace with a real option parser
27+
val options = args.asSequence()
28+
.flatMap { t -> t.split("=", limit = 1) }
29+
.chunked(2)
30+
.fold(mutableMapOf<String, MutableList<String>>()) { m, (key, value) ->
31+
m.apply {
32+
computeIfAbsent(key) { mutableListOf() }.add(value)
33+
}
34+
}
35+
36+
val envPattern = Regex("\\$\\{(\\w+)}")
37+
val capabilitiesDirectory = options["--out"]
38+
?.first()
39+
?.let { env ->
40+
envPattern.replace(env) {
41+
System.getenv(it.groups[1]?.value)
42+
}
43+
}
44+
?.run(FileSystems.getDefault()::getPath)
45+
?.apply {
46+
if (!parent.exists()) {
47+
Files.createDirectories(parent)
48+
}
49+
}
50+
?: error("--out is required")
51+
52+
capabilitiesDirectory.resolve(capabilitiesName).writeText(
53+
getArguments(K2JVMCompilerArguments::class.java)
54+
.filterNot(KotlincCapability::shouldSuppress)
55+
.asCapabilities()
56+
.asCapabilitiesBzl()
57+
.toString(),
58+
StandardCharsets.UTF_8,
59+
)
60+
61+
capabilitiesDirectory.resolve("templates.bzl").writeText(
62+
BzlDoc {
63+
assignment(
64+
"TEMPLATES",
65+
list(
66+
*Files.list(capabilitiesDirectory)
67+
.filter { it.fileName.toString().startsWith("capabilities_") }
68+
.map { "Label(${it.fileName.bzlQuote()})" }
69+
.sorted()
70+
.toArray(::arrayOfNulls),
71+
),
72+
)
73+
}.toString(),
74+
)
75+
}
76+
2177
/** Options that are either confusing, useless, or unexpected to be set outside the worker. */
2278
private val suppressedFlags = setOf(
2379
"-P",
2480
"-X",
2581
"-Xbuild-file",
2682
"-Xcompiler-plugin",
27-
"-Xcompiler-plugin",
2883
"-Xdump-declarations-to",
2984
"-Xdump-directory",
3085
"-Xdump-fqname",
@@ -45,54 +100,109 @@ object WriteKotlincCapabilities {
45100
"-script-templates",
46101
)
47102

48-
@JvmStatic
49-
fun main(vararg args: String) {
50-
// TODO: Replace with a real option parser
51-
val options = args.asSequence()
52-
.flatMap { t -> t.split("=", limit = 1) }
53-
.chunked(2)
54-
.fold(mutableMapOf<String, MutableList<String>>()) { m, (key, value) ->
55-
m.apply {
56-
computeIfAbsent(key) { mutableListOf() }.add(value)
57-
}
58-
}
59-
60-
val instance = K2JVMCompilerArguments()
103+
fun String.increment() = "$this "
104+
fun String.decrement() = substring(0, (length - 2).coerceAtLeast(0))
61105

62-
val capabilitiesName = LanguageVersion.LATEST_STABLE.run {
106+
val capabilitiesName: String by lazy {
107+
LanguageVersion.LATEST_STABLE.run {
63108
"capabilities_${major}.${minor}.bzl.com_github_jetbrains_kotlin.bazel"
64109
}
110+
}
65111

66-
val envPattern = Regex("\\$\\{(\\w+)}")
67-
val capabilitiesDirectory = options["--out"]
68-
?.first()
69-
?.let { env ->
70-
envPattern.replace(env) {
71-
System.getenv(it.groups[1]?.value)
112+
private class BzlDoc {
113+
private val HEADER = Comment(
114+
"""
115+
# Copyright ${Year.now()} The Bazel Authors. All rights reserved.
116+
#
117+
# Licensed under the Apache License, Version 2.0 (the "License");
118+
# you may not use this file except in compliance with the License.
119+
# You may obtain a copy of the License at
120+
#
121+
# http://www.apache.org/licenses/LICENSE-2.0
122+
#
123+
# Unless required by applicable law or agreed to in writing, software
124+
# distributed under the License is distributed on an "AS IS" BASIS,
125+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
126+
# See the License for the specific language governing permissions and
127+
# limitations under the License.
128+
129+
# DO NOT EDIT: generated by bazel run //src/main/kotlin/io/bazel/kotlin/generate:kotlin_release_options
130+
""".trimIndent(),
131+
)
132+
133+
val contents: MutableList<Block> = mutableListOf()
134+
135+
constructor(statements: BzlDoc.() -> Unit) {
136+
statement(HEADER)
137+
apply(statements)
138+
}
139+
140+
fun statement(vararg statements: Block) {
141+
contents.addAll(statements)
142+
}
143+
144+
class Indent(val spaces: Int = 0) {
145+
fun increment() = Indent(spaces+2)
146+
fun decrement() = Indent(max(spaces - 2, 0))
147+
override fun toString() = " ".repeat(spaces)
148+
operator fun plus(s:String?) = toString() + s
149+
}
150+
151+
fun interface Block {
152+
fun asString(indent: Indent): String?
153+
fun asString() = asString(Indent())
154+
}
155+
156+
fun interface ValueBlock : Block {
157+
fun asString(indent: Indent, map: (String) -> String): String?
158+
override fun asString(indent: Indent) = asString(indent.increment()) { it }
159+
}
160+
161+
class Comment(val contents: String) : Block {
162+
override fun asString(indent: Indent): String? = indent + contents
163+
}
164+
165+
override fun toString() = contents.mapNotNull { it.asString() }.joinToString("\n")
166+
167+
fun assignment(key: String, value: ValueBlock) {
168+
statement(
169+
Block { indent ->
170+
indent + value.asString(indent.increment()) { "$key = $it" }
171+
},
172+
)
173+
}
174+
175+
fun struct(vararg properties: Pair<String, String?>) = ValueBlock { indent, format ->
176+
properties
177+
.mapNotNull { (key, value) ->
178+
value?.let { "$indent$key = $it" }
72179
}
73-
}
74-
?: error("--out is required")
180+
.joinToString(",\n", prefix = "struct(\n", postfix = "\n${indent.decrement()})")
181+
.run(format)
182+
}
75183

76-
FileSystems.getDefault()
77-
.getPath("$capabilitiesDirectory/$capabilitiesName")
78-
.apply {
79-
if (!parent.exists()) {
80-
Files.createDirectories(parent)
184+
fun dict(vararg properties: Pair<String, ValueBlock>) = ValueBlock { indent, format ->
185+
properties
186+
.mapNotNull { (key, value) ->
187+
value.asString(indent.increment())
188+
?.let { "$indent${key.bzlQuote()} : $it" }
81189
}
82-
writeText(
83-
getArguments(K2JVMCompilerArguments::class.java)
84-
.filterNot(KotlincCapability::shouldSuppress)
85-
.asCapabilities()
86-
.asCapabilitiesBzl(),
87-
StandardCharsets.UTF_8
88-
)
89-
}
90-
.let {
91-
println("Wrote to $it")
92-
}
190+
.joinToString(",\n", prefix = "{\n", postfix = "\n${indent.decrement()}}")
191+
.run(format)
192+
}
193+
194+
fun list(vararg items: String) = ValueBlock { indent, format ->
195+
items
196+
.joinToString(
197+
separator = ",\n",
198+
prefix = "[\n",
199+
postfix = "\n${indent.decrement()}]",
200+
) { "$indent$it" }
201+
.run(format)
202+
}
93203
}
94204

95-
private fun getArguments(klass: Class<*>) : Sequence<KotlincCapability> = sequence {
205+
private fun getArguments(klass: Class<*>): Sequence<KotlincCapability> = sequence {
96206
val instance = K2JVMCompilerArguments()
97207
klass.superclass?.let {
98208
yieldAll(getArguments(it))
@@ -104,55 +214,86 @@ object WriteKotlincCapabilities {
104214
yield(
105215
KotlincCapability(
106216
flag = argument.value,
107-
default = "${ getter.invoke(instance)}",
108-
type = field.type.toString(),
109-
)
217+
default = getter.invoke(instance)?.let(Any::toString),
218+
type = StarlarkType.mapFrom(field.type),
219+
doc = argument.description,
220+
),
110221
)
111222
}
112223
}
113224
}
114225

115-
private class KotlincCapabilities(capabilities: Iterable<KotlincCapability>) :
116-
List<KotlincCapability> by capabilities.toList() {
226+
private class KotlincCapabilities(val capabilities: Iterable<KotlincCapability>) {
117227

118228
companion object {
119229
fun Sequence<KotlincCapability>.asCapabilities() = KotlincCapabilities(sorted().toList())
120230
}
121231

122-
fun asCapabilitiesBzl() = HEADER + "\n" + joinToString(
123-
",\n ",
124-
prefix = "KOTLIN_OPTS = [\n ",
125-
postfix = "\n]",
126-
transform = KotlincCapability::asCapabilityFlag,
127-
)
232+
fun asCapabilitiesBzl() = BzlDoc {
233+
assignment(
234+
"KOTLIN_OPTS",
235+
dict(
236+
*capabilities.map { capability ->
237+
capability.flag to struct(
238+
"flag" to capability.flag.bzlQuote(),
239+
"doc" to capability.doc.bzlQuote(),
240+
"default" to capability.defaultStarlarkValue(),
241+
)
242+
}.toTypedArray(),
243+
),
244+
)
245+
}
128246
}
129247

130-
private data class KotlincCapability(
131-
private val flag: String,
132-
private val default: String,
133-
private val type: String,
248+
data class KotlincCapability(
249+
val flag: String,
250+
val doc: String,
251+
private val default: String?,
252+
private val type: StarlarkType,
134253
) : Comparable<KotlincCapability> {
135254

136-
fun shouldSuppress() = flag in suppressedFlags
255+
fun shouldSuppress() = flag in suppressedFlags
256+
257+
fun defaultStarlarkValue(): String? = type.convert(default)
137258

138-
fun asCapabilityFlag() = "\"${flag}\""
139259
override fun compareTo(other: KotlincCapability): Int = flag.compareTo(other.flag)
140260
}
141261

262+
sealed class StarlarkType(val attr: String) {
142263

143-
private val HEADER = """
144-
# Copyright ${Year.now()} The Bazel Authors. All rights reserved.
145-
#
146-
# Licensed under the Apache License, Version 2.0 (the "License");
147-
# you may not use this file except in compliance with the License.
148-
# You may obtain a copy of the License at
149-
#
150-
# http://www.apache.org/licenses/LICENSE-2.0
151-
#
152-
# Unless required by applicable law or agreed to in writing, software
153-
# distributed under the License is distributed on an "AS IS" BASIS,
154-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
155-
# See the License for the specific language governing permissions and
156-
# limitations under the License.
157-
""".trimIndent()
264+
class Bool() : StarlarkType("attr.bool") {
265+
override fun convert(value: String?): String? = when (value) {
266+
"true" -> "True"
267+
else -> "False"
268+
}
269+
}
270+
271+
class Str() : StarlarkType("attr.string") {
272+
override fun convert(value: String?): String? = value?.bzlQuote() ?: "None"
273+
}
274+
275+
class StrList() : StarlarkType("attr.string_list") {
276+
override fun convert(value: String?): String =
277+
value?.let { "default = [${it.bzlQuote()}]" } ?: "[]"
278+
}
279+
280+
companion object {
281+
fun mapFrom(clazz: Class<*>) = when (clazz.canonicalName) {
282+
java.lang.Boolean.TYPE.canonicalName, java.lang.Boolean::class.java.canonicalName -> Bool()
283+
java.lang.String::class.java.canonicalName -> Str()
284+
Array<String>::class.java.canonicalName -> StrList()
285+
else -> {
286+
throw IllegalArgumentException("($clazz)is not a starlark mappable type")
287+
}
288+
}
289+
}
290+
291+
abstract fun convert(value: String?): String?
292+
}
293+
294+
private fun Any.bzlQuote(): String {
295+
var asString = toString()
296+
val quote = "\"".repeat(if ("\n" in asString || "\"" in asString) 3 else 1)
297+
return quote + asString + quote
298+
}
158299
}

src/main/starlark/core/options/opts.kotlinc.bzl

+21-1
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,28 @@ _KOPTS_ALL = {
431431
),
432432
}
433433

434+
def _merge(key, rule_defined):
435+
"""Merges rule option with compiler option."""
436+
if key not in _KOTLIN_OPTS:
437+
# No flag associated with option.
438+
return rule_defined
439+
generated = _KOTLIN_OPTS[key]
440+
merged = {k: getattr(k, rule_defined) for k in dir(rule_defined)}
441+
merged["doc"] = generated.doc
442+
merged["default"] = generated.default
443+
return struct(**merged)
444+
445+
def _no_merge(_, definition):
446+
return definition
447+
448+
_maybe_merge_definition = _merge if hasattr(_KOTLIN_OPTS, "get") else _no_merge
449+
434450
# Filters out options that are not available in current compiler release
435-
_KOPTS = {attr: defn for (attr, defn) in _KOPTS_ALL.items() if not hasattr(defn, "flag") or defn.flag in _KOTLIN_OPTS}
451+
_KOPTS = {
452+
attr: _maybe_merge_definition(attr, defn)
453+
for (attr, defn) in _KOPTS_ALL.items()
454+
if not hasattr(defn, "flag") or defn.flag in _KOTLIN_OPTS
455+
}
436456

437457
KotlincOptions = provider(
438458
fields = {

0 commit comments

Comments
 (0)