[Compiler CLI] Implement reading language version settings from environment variable

^KT-51306 Fixed
This commit is contained in:
Victor Petukhov
2022-02-15 11:48:28 +03:00
committed by teamcity
parent 43a0876c26
commit 683a3e74a0
27 changed files with 175 additions and 7 deletions
@@ -38,6 +38,7 @@ enum class CompilerSystemProperties(val property: String, val alwaysDirectAccess
COMPILE_INCREMENTAL_WITH_CLASSPATH_SNAPSHOTS("kotlin.incremental.classpath.snapshot.enabled"),
COMPILE_INCREMENTAL_WITH_ARTIFACT_TRANSFORM("kotlin.incremental.useClasspathSnapshot"),
KOTLIN_COLORS_ENABLED_PROPERTY("kotlin.colors.enabled"),
LANGUAGE_VERSION_SETTINGS("kotlin.language.settings"),
OS_NAME("os.name", alwaysDirectAccess = true),
TMP_DIR("java.io.tmpdir"),
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.cli.common.arguments
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
import org.jetbrains.kotlin.utils.SmartList
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
@@ -63,13 +64,24 @@ data class ArgumentParseErrors(
)
// Parses arguments into the passed [result] object. Errors related to the parsing will be collected into [CommonToolArguments.errors].
fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, result: A) {
fun <A : CommonToolArguments> parseCommandLineArguments(args: List<String>, result: A, overrideArguments: Boolean = false) {
val errors = result.errors ?: ArgumentParseErrors().also { result.errors = it }
val preprocessed = preprocessCommandLineArguments(args, errors)
parsePreprocessedCommandLineArguments(preprocessed, result, errors)
parsePreprocessedCommandLineArguments(preprocessed, result, errors, overrideArguments)
}
private fun <A : CommonToolArguments> parsePreprocessedCommandLineArguments(args: List<String>, result: A, errors: ArgumentParseErrors) {
fun <A : CommonToolArguments> parseCommandLineArgumentsFromEnvironment(arguments: A) {
val settingsFromEnvironment = CompilerSystemProperties.LANGUAGE_VERSION_SETTINGS.value?.takeIf { it.isNotEmpty() }
?.split(Regex("""\s""")) ?: return
parseCommandLineArguments(settingsFromEnvironment, arguments, overrideArguments = true)
}
private fun <A : CommonToolArguments> parsePreprocessedCommandLineArguments(
args: List<String>,
result: A,
errors: ArgumentParseErrors,
overrideArguments: Boolean
) {
data class ArgumentField(val property: KMutableProperty1<A, Any?>, val argument: Argument)
@Suppress("UNCHECKED_CAST")
@@ -183,14 +195,36 @@ private fun <A : CommonToolArguments> parsePreprocessedCommandLineArguments(args
errors.duplicateArguments[argument.value] = value
}
updateField(property, result, value, argument.delimiter)
updateField(property, result, value, argument.delimiter, overrideArguments)
}
result.freeArgs += freeArgs
result.internalArguments += internalArguments
result.updateInternalArguments(internalArguments, overrideArguments)
}
private fun <A : CommonToolArguments> updateField(property: KMutableProperty1<A, Any?>, result: A, value: Any, delimiter: String) {
private fun <A : CommonToolArguments> A.updateInternalArguments(
newInternalArguments: ArrayList<InternalArgument>,
overrideArguments: Boolean
) {
val filteredExistingArguments = if (overrideArguments) {
internalArguments.filter { existingArgument ->
existingArgument !is ManualLanguageFeatureSetting ||
newInternalArguments.none {
it is ManualLanguageFeatureSetting && it.languageFeature == existingArgument.languageFeature
}
}
} else internalArguments
internalArguments = filteredExistingArguments + newInternalArguments
}
private fun <A : CommonToolArguments> updateField(
property: KMutableProperty1<A, Any?>,
result: A,
value: Any,
delimiter: String,
overrideArguments: Boolean
) {
when (property.returnType.classifier) {
Boolean::class, String::class -> property.set(result, value)
Array<String>::class -> {
@@ -201,7 +235,7 @@ private fun <A : CommonToolArguments> updateField(property: KMutableProperty1<A,
}
@Suppress("UNCHECKED_CAST")
val oldValue = property.get(result) as Array<String>?
property.set(result, if (oldValue != null) arrayOf(*oldValue, *newElements) else newElements)
property.set(result, if (oldValue != null && !overrideArguments) arrayOf(*oldValue, *newElements) else newElements)
}
else -> throw IllegalStateException("Unsupported argument type: ${property.returnType}")
}
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cli.common
import org.fusesource.jansi.AnsiConsole
import org.jetbrains.kotlin.cli.common.arguments.CommonToolArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArgumentsFromEnvironment
import org.jetbrains.kotlin.cli.common.arguments.validateArguments
import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.INFO
@@ -47,6 +48,7 @@ abstract class CLITool<A : CommonToolArguments> {
): ExitCode {
val arguments = createArguments()
parseCommandLineArguments(args.asList(), arguments)
parseCommandLineArgumentsFromEnvironment(arguments)
val collector = PrintingMessageCollector(errStream, messageRenderer, arguments.verbose)
try {
@@ -0,0 +1,4 @@
$TESTDATA_DIR$/appendingArgs.kt
-d
$TEMP_DIR$
-Xallow-kotlin-package
@@ -0,0 +1 @@
-Xrender-internal-diagnostic-names
@@ -0,0 +1,5 @@
package kotlin
fun main() {
val x = 1?.dec()
}
@@ -0,0 +1,10 @@
compiler/testData/cli/jvm/readingConfigFromEnvironment/appendingArgs.kt:4:9: warning: [UNUSED_VARIABLE] Variable 'x' is never used
val x = 1?.dec()
^
compiler/testData/cli/jvm/readingConfigFromEnvironment/appendingArgs.kt:4:13: warning: [SAFE_CALL_WILL_CHANGE_NULLABILITY] Safe call on a non-null receiver will have nullable type in future releases
val x = 1?.dec()
^
compiler/testData/cli/jvm/readingConfigFromEnvironment/appendingArgs.kt:4:14: warning: [UNNECESSARY_SAFE_CALL] Unnecessary safe call on a non-null receiver of type Int
val x = 1?.dec()
^
OK
@@ -0,0 +1,4 @@
public class Annotated {
@org.jetbrains.annotations.Nullable
public static String bar() { return ""; }
}
@@ -0,0 +1,6 @@
$TESTDATA_DIR$/overridingArgs.kt
$TESTDATA_DIR$/java
$FOREIGN_ANNOTATIONS_DIR$
-d
$TEMP_DIR$
-Xnullability-annotations=@org.jetbrains.annotations\:warn
@@ -0,0 +1 @@
-Xnullability-annotations=@org.jetbrains.annotations:ignore
@@ -0,0 +1,3 @@
fun main() {
Annotated.bar().length
}
@@ -0,0 +1 @@
OK
@@ -0,0 +1,5 @@
$TESTDATA_DIR$/overridingLv.kt
-d
$TEMP_DIR$
-language-version
1.7
@@ -0,0 +1,2 @@
-language-version
1.1
@@ -0,0 +1,3 @@
fun main() {
}
@@ -0,0 +1,2 @@
error: language version 1.1 is no longer supported; please, use version 1.3 or greater.
COMPILATION_ERROR
@@ -0,0 +1,4 @@
$TESTDATA_DIR$/overridingXx.kt
-d
$TEMP_DIR$
-XXLanguage\:+ApproximateIntegerLiteralTypesInReceiverPosition
@@ -0,0 +1 @@
-XXLanguage:-ApproximateIntegerLiteralTypesInReceiverPosition
@@ -0,0 +1,3 @@
fun foo(ttlMillis: Long = 5 * 60 * 1000) {}
const val cacheSize: Long = 4096 * 4
@@ -0,0 +1,13 @@
warning: ATTENTION!
This build uses unsafe internal compiler arguments:
-XXLanguage:-ApproximateIntegerLiteralTypesInReceiverPosition
This mode is not recommended for production use,
as no stability/compatibility guarantees are given on
compiler or generated code. Use it at your own risk!
compiler/testData/cli/jvm/readingConfigFromEnvironment/overridingXx.kt:1:9: warning: parameter 'ttlMillis' is never used
fun foo(ttlMillis: Long = 5 * 60 * 1000) {}
^
OK
@@ -0,0 +1,3 @@
$TESTDATA_DIR$/simple.kt
-d
$TEMP_DIR$
@@ -0,0 +1 @@
-Xrender-internal-diagnostic-names
@@ -0,0 +1,3 @@
fun main() {
val x = 1 + ""
}
@@ -0,0 +1,10 @@
compiler/testData/cli/jvm/readingConfigFromEnvironment/simple.kt:2:15: error: [NONE_APPLICABLE] None of the following functions can be called with the arguments supplied:
public final operator fun plus(other: Byte): Int defined in kotlin.Int
public final operator fun plus(other: Double): Double defined in kotlin.Int
public final operator fun plus(other: Float): Float defined in kotlin.Int
public final operator fun plus(other: Int): Int defined in kotlin.Int
public final operator fun plus(other: Long): Long defined in kotlin.Int
public final operator fun plus(other: Short): Int defined in kotlin.Int
val x = 1 + ""
^
COMPILATION_ERROR
@@ -27,6 +27,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.checkers.ThirdPartyAnnotationPathsKt;
import org.jetbrains.kotlin.cli.common.CLITool;
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties;
import org.jetbrains.kotlin.cli.common.ExitCode;
import org.jetbrains.kotlin.cli.common.Usage;
import org.jetbrains.kotlin.cli.js.K2JSCompiler;
@@ -102,6 +103,12 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
private void doTest(@NotNull String fileName, @NotNull CLITool<?> compiler) {
System.setProperty("java.awt.headless", "true");
File environmentTestConfig = new File(fileName.replaceFirst("\\.args$", ".env"));
if (environmentTestConfig.exists()) {
CompilerSystemProperties.LANGUAGE_VERSION_SETTINGS.setValue(FilesKt.readText(environmentTestConfig, Charsets.UTF_8));
}
Pair<String, ExitCode> outputAndExitCode = executeCompilerGrabOutput(compiler, readArgs(fileName, tmpdir.getPath()));
String actual = getNormalizedCompilerOutput(
outputAndExitCode.getFirst(), outputAndExitCode.getSecond(), new File(fileName).getParent()
@@ -250,6 +250,7 @@ fun generateJUnit3CompilerTests(args: Array<String>) {
}
testClass<AbstractCliTest> {
model("cli/jvm/readingConfigFromEnvironment", extension = "args", testMethod = "doJvmTest", recursive = false)
model("cli/jvm", extension = "args", testMethod = "doJvmTest", recursive = false)
model("cli/js", extension = "args", testMethod = "doJsTest", recursive = false)
model("cli/js-dce", extension = "args", testMethod = "doJsDceTest", recursive = false)
@@ -19,6 +19,44 @@ import java.util.regex.Pattern;
@SuppressWarnings("all")
@RunWith(JUnit3RunnerWithInners.class)
public class CliTestGenerated extends AbstractCliTest {
@TestMetadata("compiler/testData/cli/jvm/readingConfigFromEnvironment")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ReadingConfigFromEnvironment extends AbstractCliTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doJvmTest, this, testDataFilePath);
}
public void testAllFilesPresentInReadingConfigFromEnvironment() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/cli/jvm/readingConfigFromEnvironment"), Pattern.compile("^(.+)\\.args$"), null, false);
}
@TestMetadata("appendingArgs.args")
public void testAppendingArgs() throws Exception {
runTest("compiler/testData/cli/jvm/readingConfigFromEnvironment/appendingArgs.args");
}
@TestMetadata("overridingArgs.args")
public void testOverridingArgs() throws Exception {
runTest("compiler/testData/cli/jvm/readingConfigFromEnvironment/overridingArgs.args");
}
@TestMetadata("overridingLv.args")
public void testOverridingLv() throws Exception {
runTest("compiler/testData/cli/jvm/readingConfigFromEnvironment/overridingLv.args");
}
@TestMetadata("overridingXx.args")
public void testOverridingXx() throws Exception {
runTest("compiler/testData/cli/jvm/readingConfigFromEnvironment/overridingXx.args");
}
@TestMetadata("simple.args")
public void testSimple() throws Exception {
runTest("compiler/testData/cli/jvm/readingConfigFromEnvironment/simple.args");
}
}
@TestMetadata("compiler/testData/cli/jvm")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)