Support -Xargfile in all scenarios; refactor & prettify code

Perform command line argument preprocessing in the beginning of
parseCommandLineArguments, so that argfiles are expanded in all
scenarios, not just when the compiler is invoked via
K2{JVM,JS}Compiler.exec
This commit is contained in:
Alexander Udalov
2018-05-25 14:18:04 +02:00
committed by Dmitry Savvinov
parent 43467516ef
commit 61902e1fd5
4 changed files with 32 additions and 32 deletions
@@ -54,11 +54,16 @@ data class ArgumentParseErrors(
var argumentWithoutValue: String? = null, var argumentWithoutValue: String? = null,
val argfileErrors: MutableList<String> = SmartList<String>() val argfileErrors: MutableList<String> = SmartList()
) )
// Parses arguments into the passed [result] object. Errors related to the parsing will be collected into [CommonToolArguments.errors]. // 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) {
val preprocessed = preprocessCommandLineArguments(args, result.errors)
parsePreprocessedCommandLineArguments(preprocessed, result)
}
private fun <A : CommonToolArguments> parsePreprocessedCommandLineArguments(args: List<String>, result: A) {
data class ArgumentField(val property: KMutableProperty1<A, Any?>, val argument: Argument) data class ArgumentField(val property: KMutableProperty1<A, Any?>, val argument: Argument)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -5,39 +5,42 @@
package org.jetbrains.kotlin.cli.common.arguments package org.jetbrains.kotlin.cli.common.arguments
import java.io.* import java.io.File
import java.nio.charset.StandardCharsets import java.io.FileNotFoundException
import java.io.IOException
import java.io.Reader
private val experimentalArgfileArgument = "-Xargfile" private const val EXPERIMENTAL_ARGFILE_ARGUMENT = "-Xargfile"
private val QUOTATION_MARK = '"'
private val BACKSLASH = '\\' private const val QUOTATION_MARK = '"'
private val WHITESPACE = ' ' private const val BACKSLASH = '\\'
private val NEWLINE = '\n' private const val WHITESPACE = ' '
private const val NEWLINE = '\n'
/** /**
* Performs initial preprocessing of arguments, passed to the compiler. * Performs initial preprocessing of arguments, passed to the compiler.
* This is done prior to *any* arguments parsing, and result of preprocessing * This is done prior to *any* arguments parsing, and result of preprocessing
* will be used instead of actual passed arguments. * will be used instead of actual passed arguments.
*/ */
fun <A : CommonToolArguments> preprocessCommandLineArguments(args: List<String>, result: A): List<String> = internal fun preprocessCommandLineArguments(args: List<String>, errors: ArgumentParseErrors): List<String> =
args.flatMap { args.flatMap {
if (it.isArgumentForArgfile) if (it.isArgumentForArgfile)
File(it.argfilePath).expand(result) File(it.argfilePath).expand(errors)
else else
listOf(it) listOf(it)
} }
private fun <A : CommonToolArguments> File.expand(result: A): List<String> { private fun File.expand(errors: ArgumentParseErrors): List<String> {
return try { return try {
bufferedReader(Charsets.UTF_8).use { bufferedReader(Charsets.UTF_8).use {
generateSequence { it.parseNextArgument() }.toList() generateSequence { it.parseNextArgument() }.toList()
} }
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
// Process FNFE separately to render absolutePath in error message // Process FNFE separately to render absolutePath in error message
result.errors.argfileErrors += "Argfile not found: $absolutePath" errors.argfileErrors += "Argfile not found: $absolutePath"
emptyList() emptyList()
} catch (e: IOException) { } catch (e: IOException) {
result.errors.argfileErrors += "Error while reading argfile: $e" errors.argfileErrors += "Error while reading argfile: $e"
emptyList() emptyList()
} }
} }
@@ -69,9 +72,9 @@ private fun Reader.consumeRestOfEscapedSequence(sb: StringBuilder) {
} }
private val String.argfilePath: String private val String.argfilePath: String
get() = removePrefix("$experimentalArgfileArgument=") get() = removePrefix("$EXPERIMENTAL_ARGFILE_ARGUMENT=")
// Note that currently we use only experimental syntax for passing argfiles // Note that currently we use only experimental syntax for passing argfiles
// In 1.3 we can support also javac-like syntax `@argfile` // In 1.3 we can support also javac-like syntax `@argfile`
private val String.isArgumentForArgfile: Boolean private val String.isArgumentForArgfile: Boolean
get() = startsWith("$experimentalArgfileArgument=") get() = startsWith("$EXPERIMENTAL_ARGFILE_ARGUMENT=")
@@ -41,8 +41,7 @@ abstract class CLITool<A : CommonToolArguments> {
args: Array<out String> args: Array<out String>
): ExitCode { ): ExitCode {
val arguments = createArguments() val arguments = createArguments()
val preprocessedArguments = preprocessCommandLineArguments(args.asList(), arguments) parseCommandLineArguments(args.asList(), arguments)
parseCommandLineArguments(preprocessedArguments, arguments)
val collector = PrintingMessageCollector(errStream, messageRenderer, arguments.verbose) val collector = PrintingMessageCollector(errStream, messageRenderer, arguments.verbose)
try { try {
@@ -106,8 +105,7 @@ abstract class CLITool<A : CommonToolArguments> {
// Used in kotlin-maven-plugin (KotlinCompileMojoBase) and in kotlin-gradle-plugin (KotlinJvmOptionsImpl, KotlinJsOptionsImpl) // Used in kotlin-maven-plugin (KotlinCompileMojoBase) and in kotlin-gradle-plugin (KotlinJvmOptionsImpl, KotlinJsOptionsImpl)
fun parseArguments(args: Array<out String>, arguments: A) { fun parseArguments(args: Array<out String>, arguments: A) {
val preprocessed = preprocessCommandLineArguments(args.asList(), arguments) parseCommandLineArguments(args.asList(), arguments)
parseCommandLineArguments(preprocessed, arguments)
val message = validateArguments(arguments.errors) val message = validateArguments(arguments.errors)
if (message != null) { if (message != null) {
throw IllegalArgumentException(message) throw IllegalArgumentException(message)
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.cli; package org.jetbrains.kotlin.cli;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilKt;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import kotlin.Pair; import kotlin.Pair;
import kotlin.collections.CollectionsKt; import kotlin.collections.CollectionsKt;
@@ -31,7 +30,6 @@ import org.jetbrains.kotlin.cli.js.K2JSCompiler;
import org.jetbrains.kotlin.cli.js.dce.K2JSDce; import org.jetbrains.kotlin.cli.js.dce.K2JSDce;
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler; import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler;
import org.jetbrains.kotlin.codegen.TestUtilsKt;
import org.jetbrains.kotlin.config.KotlinCompilerVersion; import org.jetbrains.kotlin.config.KotlinCompilerVersion;
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion; import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion;
import org.jetbrains.kotlin.test.CompilerTestUtil; import org.jetbrains.kotlin.test.CompilerTestUtil;
@@ -44,14 +42,14 @@ import org.jetbrains.kotlin.utils.StringsKt;
import org.junit.Assert; import org.junit.Assert;
import java.io.File; import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public abstract class AbstractCliTest extends TestCaseWithTmpdir { public abstract class AbstractCliTest extends TestCaseWithTmpdir {
private static final String TESTDATA_DIR = "$TESTDATA_DIR$"; private static final String TESTDATA_DIR = "$TESTDATA_DIR$";
private static final String EXPERIMENTAL_ARGFILE_ARGUMENT_PREFIX = "-Xargfile=";
public static Pair<String, ExitCode> executeCompilerGrabOutput(@NotNull CLITool<?> compiler, @NotNull List<String> args) { public static Pair<String, ExitCode> executeCompilerGrabOutput(@NotNull CLITool<?> compiler, @NotNull List<String> args) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
@@ -202,21 +200,17 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
String argWithTestPathsReplaced = replaceTestPaths(argWithColonsReplaced, testDataDir, tempDir); String argWithTestPathsReplaced = replaceTestPaths(argWithColonsReplaced, testDataDir, tempDir);
if (isArgfileArgument(arg)) { if (arg.startsWith(EXPERIMENTAL_ARGFILE_ARGUMENT_PREFIX)) {
return mockArgfile(argWithTestPathsReplaced, testDataDir, tempDir); return mockArgfile(argWithTestPathsReplaced, testDataDir, tempDir);
} else { }
else {
return argWithTestPathsReplaced; return argWithTestPathsReplaced;
} }
} }
private static boolean isArgfileArgument(@NotNull String arg) {
return arg.startsWith("-Xargfile=");
}
// Create new temp. argfile with all test paths replaced and return argfile-argument pointing to that file // Create new temp. argfile with all test paths replaced and return argfile-argument pointing to that file
private static String mockArgfile(@NotNull String argfileArgument, @NotNull String testDataDir, @NotNull String tempDir) { private static String mockArgfile(@NotNull String argfileArgument, @NotNull String testDataDir, @NotNull String tempDir) {
int firstIndexOfArgfilePath = "-Xargfile=".length(); String argfilePath = kotlin.text.StringsKt.substringAfter(argfileArgument, EXPERIMENTAL_ARGFILE_ARGUMENT_PREFIX, argfileArgument);
String argfilePath = argfileArgument.substring(firstIndexOfArgfilePath);
File argfile = new File(argfilePath); File argfile = new File(argfilePath);
if (argfile.exists()) { if (argfile.exists()) {
@@ -224,7 +218,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir {
String oldArgfileContent = FilesKt.readText(argfile, Charsets.UTF_8); String oldArgfileContent = FilesKt.readText(argfile, Charsets.UTF_8);
String newArgfileContent = replaceTestPaths(oldArgfileContent, testDataDir, tempDir); String newArgfileContent = replaceTestPaths(oldArgfileContent, testDataDir, tempDir);
FilesKt.writeText(mockArgfile, newArgfileContent, Charsets.UTF_8); FilesKt.writeText(mockArgfile, newArgfileContent, Charsets.UTF_8);
return "-Xargfile=" + mockArgfile.getAbsolutePath(); return EXPERIMENTAL_ARGFILE_ARGUMENT_PREFIX + mockArgfile.getAbsolutePath();
} else { } else {
return argfileArgument; return argfileArgument;
} }