CLI library rework (#3215)

This commit is contained in:
LepilkinaElena
2019-09-06 17:49:12 +03:00
committed by GitHub
parent 51d03ece0a
commit 8e0e346492
66 changed files with 1494 additions and 1099 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-compiler:$kotlinVersion"
compile project(':Interop:Indexer')
compile "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
compile project(path: ":endorsedLibraries:kliopt", configuration: "jvmRuntimeElements")
compile project(path: ":endorsedLibraries:kotlinx.cli", configuration: "jvmRuntimeElements")
}
compileKotlin {
@@ -7,7 +7,7 @@ import org.jetbrains.kotlin.native.interop.gen.jvm.prepareTool
import org.jetbrains.kotlin.native.interop.indexer.NativeLibraryHeaders
import org.jetbrains.kotlin.native.interop.indexer.getHeaderPaths
import org.jetbrains.kotlin.native.interop.tool.CInteropArguments
import org.jetbrains.kliopt.ArgParser
import kotlinx.cli.ArgParser
import java.io.File
fun defFileDependencies(args: Array<String>) {
@@ -16,85 +16,83 @@
package org.jetbrains.kotlin.native.interop.tool
import org.jetbrains.kliopt.*
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.*
const val HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX = "headerFilterAdditionalSearchPrefix"
const val NODEFAULTLIBS = "nodefaultlibs"
const val NODEFAULTLIBS_DEPRECATED = "nodefaultlibs"
const val NODEFAULTLIBS = "no-default-libs"
const val NOENDORSEDLIBS = "no-endorsed-libs"
const val PURGE_USER_LIBS = "Xpurge-user-libs"
const val TEMP_DIR = "Xtemporary-files-dir"
// TODO: unify camel and snake cases.
// Possible solution is to accept both cases
open class CommonInteropArguments(val argParser: ArgParser) {
val verbose by argParser.option(ArgType.Boolean, description = "Enable verbose logging output", defaultValue = false)
val flavor by argParser.option(ArgType.Choice(listOf("jvm", "native", "wasm")), description = "Interop target",
defaultValue = "jvm")
val verbose by argParser.option(ArgType.Boolean, description = "Enable verbose logging output").default(false)
val flavor by argParser.option(ArgType.Choice(listOf("jvm", "native", "wasm")), description = "Interop target")
.default("jvm")
val pkg by argParser.option(ArgType.String, description = "place generated bindings to the package")
val output by argParser.option(ArgType.String, shortName = "o", description = "specifies the resulting library file",
defaultValue = "nativelib")
val libraryPath by argParser.options(ArgType.String, description = "add a library search path",
multiple = true, delimiter = ",")
val staticLibrary by argParser.options(ArgType.String, description = "embed static library to the result",
multiple = true, delimiter = ",")
val generated by argParser.option(ArgType.String, description = "place generated bindings to the directory",
defaultValue = System.getProperty("user.dir"))
val natives by argParser.option(ArgType.String, description = "where to put the built native files",
defaultValue = System.getProperty("user.dir"))
val library by argParser.options(ArgType.String, shortName = "l", description = "library to use for building",
multiple = true)
val repo by argParser.options(ArgType.String, shortName = "r", description = "repository to resolve dependencies",
multiple = true)
val output by argParser.option(ArgType.String, shortName = "o", description = "specifies the resulting library file")
.default("nativelib")
val libraryPath by argParser.option(ArgType.String, description = "add a library search path")
.multiple().delimiter(",")
val staticLibrary by argParser.option(ArgType.String, description = "embed static library to the result")
.multiple().delimiter(",")
val generated by argParser.option(ArgType.String, description = "place generated bindings to the directory")
.default(System.getProperty("user.dir"))
val natives by argParser.option(ArgType.String, description = "where to put the built native files")
.default(System.getProperty("user.dir"))
val library by argParser.option(ArgType.String, shortName = "l", description = "library to use for building")
.multiple()
val repo by argParser.option(ArgType.String, shortName = "r", description = "repository to resolve dependencies")
.multiple()
val nodefaultlibs by argParser.option(ArgType.Boolean, NODEFAULTLIBS,
description = "don't link the libraries from dist/klib automatically").default(false)
val nodefaultlibsDeprecated by argParser.option(ArgType.Boolean, NODEFAULTLIBS_DEPRECATED,
description = "don't link the libraries from dist/klib automatically",
defaultValue = false)
deprecatedWarning = "Old form of flag. Please, use $NODEFAULTLIBS.").default(false)
val noendorsedlibs by argParser.option(ArgType.Boolean, NOENDORSEDLIBS,
description = "don't link the endorsed libraries from dist automatically").default(false)
val purgeUserLibs by argParser.option(ArgType.Boolean, PURGE_USER_LIBS,
description = "don't link unused libraries even explicitly specified", defaultValue = false)
description = "don't link unused libraries even explicitly specified").default(false)
val tempDir by argParser.option(ArgType.String, TEMP_DIR,
description = "save temporary files to the given directory")
}
class CInteropArguments(argParser: ArgParser =
ArgParser("cinterop", useDefaultHelpShortName = false,
ArgParser("cinterop",
prefixStyle = ArgParser.OPTION_PREFIX_STYLE.JVM)): CommonInteropArguments(argParser) {
val target by argParser.option(ArgType.String, description = "native target to compile to", defaultValue = "host")
val target by argParser.option(ArgType.String, description = "native target to compile to").default("host")
val def by argParser.option(ArgType.String, description = "the library definition file")
val header by argParser.options(ArgType.String, description = "header file to produce kotlin bindings for",
multiple = true, delimiter = ",")
val shortHeaderForm by argParser.options(ArgType.String, "h", description = "header file to produce kotlin bindings for",
multiple = true, delimiter = ",", deprecatedWarning = "Option -h is deprecated. Please use -header.")
val headerFilterPrefix by argParser.options(ArgType.String, HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX, "hfasp",
"header file to produce kotlin bindings for", multiple = true, delimiter = ",")
val compilerOpts by argParser.options(ArgType.String,
description = "additional compiler options (allows to add several options separated by spaces)",
multiple = true, delimiter = " ")
val compilerOptions by argParser.options(ArgType.String, "compiler-options",
description = "additional compiler options (allows to add several options separated by spaces)",
multiple = true, delimiter = " ")
val linkerOpts by argParser.options(ArgType.String, "linkerOpts",
description = "additional linker options (allows to add several options separated by spaces)",
multiple = true, delimiter = " ")
val linkerOptions by argParser.options(ArgType.String, "linker-options",
description = "additional linker options (allows to add several options separated by spaces)",
multiple = true, delimiter = " ")
val compilerOption by argParser.options(ArgType.String, "compiler-option",
description = "additional compiler option", multiple = true)
val linkerOption by argParser.options(ArgType.String, "linker-option",
description = "additional linker option", multiple = true)
val copt by argParser.options(ArgType.String,
description = "additional compiler options (allows to add several options separated by spaces)",
multiple = true, delimiter = " ",
deprecatedWarning = "Option -copt is deprecated. Please use -compiler-options.")
val lopt by argParser.options(ArgType.String,
description = "additional linker options (allows to add several options separated by spaces)",
multiple = true, delimiter = " ",
deprecatedWarning = "Option -lopt is deprecated. Please use -linker-options.")
val header by argParser.option(ArgType.String, description = "header file to produce kotlin bindings for")
.multiple().delimiter(",")
val headerFilterPrefix by argParser.option(ArgType.String, HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX, "hfasp",
"header file to produce kotlin bindings for").multiple().delimiter(",")
val compilerOpts by argParser.option(ArgType.String,
description = "additional compiler options (allows to add several options separated by spaces)")
.multiple().delimiter(" ")
val compilerOptions by argParser.option(ArgType.String, "compiler-options",
description = "additional compiler options (allows to add several options separated by spaces)")
.multiple().delimiter(" ")
val linkerOpts = argParser.option(ArgType.String, "linkerOpts",
description = "additional linker options (allows to add several options separated by spaces)")
.multiple().delimiter(" ")
val linkerOptions = argParser.option(ArgType.String, "linker-options",
description = "additional linker options (allows to add several options separated by spaces)")
.multiple().delimiter(" ")
val compilerOption by argParser.option(ArgType.String, "compiler-option",
description = "additional compiler option").multiple()
val linkerOption = argParser.option(ArgType.String, "linker-option",
description = "additional linker option").multiple()
val linker by argParser.option(ArgType.String, description = "use specified linker")
}
class JSInteropArguments(argParser: ArgParser = ArgParser("jsinterop", useDefaultHelpShortName = false,
class JSInteropArguments(argParser: ArgParser = ArgParser("jsinterop",
prefixStyle = ArgParser.OPTION_PREFIX_STYLE.JVM)): CommonInteropArguments(argParser) {
val target by argParser.option(ArgType.Choice(listOf("wasm32")),
description = "wasm target to compile to", defaultValue = "wasm32")
description = "wasm target to compile to").default("wasm32")
}
internal fun warn(msg: String) {
@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.native.interop.gen.*
import org.jetbrains.kotlin.native.interop.gen.wasm.processIdlLib
import org.jetbrains.kotlin.native.interop.indexer.*
import org.jetbrains.kotlin.native.interop.tool.*
import org.jetbrains.kliopt.ArgParser
import kotlinx.cli.ArgParser
import java.io.File
import java.lang.IllegalArgumentException
import java.nio.file.*
@@ -175,16 +175,15 @@ private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> =
val tool = prepareTool(cinteropArguments.target, flavor)
val def = DefFile(defFile, tool.substitutions)
val isLinkerOptsSetByUser = (cinteropArguments.argParser.getOrigin("linkerOpts") == ArgParser.ValueOrigin.SET_BY_USER) ||
(cinteropArguments.argParser.getOrigin("linker-option") == ArgParser.ValueOrigin.SET_BY_USER) ||
(cinteropArguments.argParser.getOrigin("linker-options") == ArgParser.ValueOrigin.SET_BY_USER) ||
(cinteropArguments.argParser.getOrigin("lopt") == ArgParser.ValueOrigin.SET_BY_USER)
val isLinkerOptsSetByUser = (cinteropArguments.linkerOpts.valueOrigin == ArgParser.ValueOrigin.SET_BY_USER) ||
(cinteropArguments.linkerOptions.valueOrigin == ArgParser.ValueOrigin.SET_BY_USER) ||
(cinteropArguments.linkerOption.valueOrigin == ArgParser.ValueOrigin.SET_BY_USER)
if (flavorName == "native" && isLinkerOptsSetByUser) {
warn("-linker-option(s)/-linkerOpts/-lopt option is not supported by cinterop. Please add linker options to .def file or binary compilation instead.")
warn("-linker-option(s)/-linkerOpts option is not supported by cinterop. Please add linker options to .def file or binary compilation instead.")
}
val additionalLinkerOpts = cinteropArguments.linkerOpts.toTypedArray() + cinteropArguments.linkerOption.toTypedArray() +
cinteropArguments.linkerOptions.toTypedArray() + cinteropArguments.lopt.toTypedArray()
val additionalLinkerOpts = cinteropArguments.linkerOpts.value.toTypedArray() + cinteropArguments.linkerOption.value.toTypedArray() +
cinteropArguments.linkerOptions.value.toTypedArray()
val verbose = cinteropArguments.verbose
val language = selectNativeLanguage(def.config)
@@ -305,10 +304,9 @@ internal fun buildNativeLibrary(
arguments: CInteropArguments,
imports: ImportsImpl
): NativeLibrary {
val additionalHeaders = (arguments.header + arguments.shortHeaderForm).toTypedArray()
val additionalHeaders = (arguments.header).toTypedArray()
val additionalCompilerOpts = (arguments.compilerOpts +
arguments.compilerOptions + arguments.compilerOption +
arguments.copt).toTypedArray()
arguments.compilerOptions + arguments.compilerOption).toTypedArray()
val headerFiles = def.config.headers + additionalHeaders
val language = selectNativeLanguage(def.config)
@@ -3,7 +3,7 @@ package org.jetbrains.kotlin.native.interop.gen.wasm
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.native.interop.gen.argsToCompiler
import org.jetbrains.kotlin.native.interop.gen.wasm.idl.*
import org.jetbrains.kliopt.ArgParser
import kotlinx.cli.ArgParser
import org.jetbrains.kotlin.native.interop.tool.JSInteropArguments
fun kotlinHeader(packageName: String): String {
+1 -1
View File
@@ -191,7 +191,7 @@ targetList.each { target ->
"-Djava.library.path=${project.buildDir}/nativelibs/$hostName",
]
def defaultArgs = ['-nopack', '-nostdlib', '-nodefaultlibs']
def defaultArgs = ['-nopack', '-nostdlib', '-no-default-libs', '-no-endorsed-libs']
if (target != "wasm32") defaultArgs += '-g'
def konanArgs = [*defaultArgs,
'-target', target,
@@ -111,6 +111,7 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
with(configuration) {
put(NODEFAULTLIBS, arguments.nodefaultlibs)
put(NOENDORSEDLIBS, arguments.noendorsedlibs)
put(NOSTDLIB, arguments.nostdlib)
put(NOPACK, arguments.nopack)
put(NOMAIN, arguments.nomain)
@@ -57,9 +57,12 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
valueDescription = "<path>", description = "Include the native bitcode library", delimiter = "")
var nativeLibraries: Array<String>? = null
@Argument(value = "-nodefaultlibs", description = "Don't link the libraries from dist/klib automatically")
@Argument(value = "-no-default-libs", deprecatedName = "-nodefaultlibs", description = "Don't link the libraries from dist/klib automatically")
var nodefaultlibs: Boolean = false
@Argument(value = "-no-endorsed-libs", description = "Don't link the endorsed libraries from dist automatically")
var noendorsedlibs: Boolean = false
@Argument(value = "-nomain", description = "Assume 'main' entry point to be provided by external libraries")
var nomain: Boolean = false
@@ -128,7 +128,8 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
resolver.resolveWithDependencies(
unresolvedLibraries + includedLibraryFiles.map { UnresolvedLibrary(it.absolutePath, null) },
noStdLib = configuration.getBoolean(KonanConfigKeys.NOSTDLIB),
noDefaultLibs = configuration.getBoolean(KonanConfigKeys.NODEFAULTLIBS)
noDefaultLibs = configuration.getBoolean(KonanConfigKeys.NODEFAULTLIBS),
noEndorsedLibs = configuration.getBoolean(KonanConfigKeys.NOENDORSEDLIBS)
)
}
@@ -66,6 +66,8 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create("native library file paths")
val NODEFAULTLIBS: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create("don't link with the default libraries")
val NOENDORSEDLIBS: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create("don't link with the endorsed libraries")
val NOMAIN: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create("assume 'main' entry point to be provided by external libraries")
val NOSTDLIB: CompilerConfigurationKey<Boolean>
@@ -826,7 +826,7 @@ internal val ModuleDescriptor.namePrefix: String get() {
internal fun abbreviate(name: String): String {
val normalizedName = name
.capitalize()
.replace('-', '_')
.replace("-|\\.".toRegex(), "_")
val uppers = normalizedName.filterIndexed { index, character -> index == 0 || character.isUpperCase() }
if (uppers.length >= 3) return uppers
+1
View File
@@ -3225,6 +3225,7 @@ void createInterop(String name, Closure conf) {
interop(name, targets: [target.name]) {
conf(it)
noDefaultLibs(true)
noEndorsedLibs(true)
baseDir "$testOutputLocal/$name"
}
}
@@ -301,7 +301,7 @@ class NativeInteropPlugin implements Plugin<Project> {
prj.dependencies {
interopStubGenerator project(path: ":Interop:StubGenerator")
interopStubGenerator project(path: ":endorsedLibraries:kliopt", configuration: "jvmRuntimeElements")
interopStubGenerator project(path: ":endorsedLibraries:kotlinx.cli", configuration: "jvmRuntimeElements")
}
// FIXME: choose tasks more wisely
@@ -137,7 +137,7 @@ open class BenchmarkingPlugin: Plugin<Project> {
project.configurations.getByName(nativeMain.implementationConfigurationName).apply {
// Exclude dependencies already included into K/N distribution (aka endorsed libraries).
exclude(mapOf("module" to "kliopt"))
exclude(mapOf("module" to "kotlinx.cli"))
}
repositories.maven {
+1 -1
View File
@@ -286,7 +286,7 @@ task distCompiler(type: Copy) {
into('konan/lib')
}
from(project(':endorsedLibraries:kliopt').file('build/libs')) {
from(project(':endorsedLibraries:kotlinx.cli').file('build/libs')) {
into('konan/lib')
}
+2 -2
View File
@@ -89,13 +89,13 @@ SHARED_JAR="${KONAN_HOME}/konan/lib/shared.jar"
EXTRACTED_METADATA_JAR="${KONAN_HOME}/konan/lib/konan.metadata.jar"
EXTRACTED_SERIALIZER_JAR="${KONAN_HOME}/konan/lib/konan.serializer.jar"
KLIB_JAR="${KONAN_HOME}/konan/lib/klib.jar"
KLIOPT_JAR="${KONAN_HOME}/konan/lib/kliopt-jvm.jar"
KOTLINX_CLI_JAR="${KONAN_HOME}/konan/lib/kotlinx.cli-jvm.jar"
UTILITIES_JAR="${KONAN_HOME}/konan/lib/utilities.jar"
NATIVE_UTILS_JAR="${KONAN_HOME}/konan/lib/kotlin-native-utils.jar"
UTIL_IO_JAR="${KONAN_HOME}/konan/lib/kotlin-util-io.jar"
UTIL_KLIB_JAR="${KONAN_HOME}/konan/lib/kotlin-util-klib.jar"
TROVE_JAR="${KONAN_HOME}/konan/lib/trove4j.jar"
KONAN_CLASSPATH="$KOTLIN_JAR:$KOTLIN_STDLIB_JAR:$KOTLIN_REFLECT_JAR:$KOTLIN_SCRIPT_RUNTIME_JAR:$INTEROP_JAR:$STUB_GENERATOR_JAR:$INTEROP_INDEXER_JAR:$KONAN_JAR:$KLIB_JAR:$UTILITIES_JAR:$SHARED_JAR:$EXTRACTED_METADATA_JAR:$EXTRACTED_SERIALIZER_JAR:$NATIVE_UTILS_JAR:$TROVE_JAR:$UTIL_IO_JAR:$UTIL_KLIB_JAR:$KLIOPT_JAR"
KONAN_CLASSPATH="$KOTLIN_JAR:$KOTLIN_STDLIB_JAR:$KOTLIN_REFLECT_JAR:$KOTLIN_SCRIPT_RUNTIME_JAR:$INTEROP_JAR:$STUB_GENERATOR_JAR:$INTEROP_INDEXER_JAR:$KONAN_JAR:$KLIB_JAR:$UTILITIES_JAR:$SHARED_JAR:$EXTRACTED_METADATA_JAR:$EXTRACTED_SERIALIZER_JAR:$NATIVE_UTILS_JAR:$TROVE_JAR:$UTIL_IO_JAR:$UTIL_KLIB_JAR:$KOTLINX_CLI_JAR"
TOOL_CLASS=org.jetbrains.kotlin.cli.utilities.MainKt
LIBCLANG_DISABLE_CRASH_RECOVERY=1 \
+2 -2
View File
@@ -55,7 +55,7 @@ set "NATIVE_LIB=%_KONAN_HOME%\konan\nativelib"
set "KONAN_LIB=%_KONAN_HOME%\konan\lib"
set "SHARED_JAR=%KONAN_LIB%\shared.jar"
set "KLIOPT_JAR=%KONAN_LIB%\kliopt-jvm.jar"
set "KOTLINX_CLI_JAR=%KONAN_LIB%\kotlinx.cli-jvm.jar"
set "EXTRACTED_METADATA_JAR=%KONAN_LIB%\konan.metadata.jar"
set "EXTRACTED_SERIALIZER_JAR=%KONAN_LIB%\konan.serializer.jar"
set "INTEROP_INDEXER_JAR=%KONAN_LIB%\Indexer.jar"
@@ -73,7 +73,7 @@ set "UTIL_IO_JAR=%KONAN_LIB%\kotlin-util-io.jar"
set "UTIL_KLIB_JAR=%KONAN_LIB%\kotlin-util-klib.jar"
set TROVE_JAR="%KONAN_LIB%\lib\trove4j.jar"
set "KONAN_CLASSPATH=%KOTLIN_JAR%;%KOTLIN_STDLIB_JAR%;%KOTLIN_REFLECT_JAR%;%KOTLIN_SCRIPT_RUNTIME_JAR%;%INTEROP_RUNTIME_JAR%;%KONAN_JAR%;%STUB_GENERATOR_JAR%;%INTEROP_INDEXER_JAR%;%SHARED_JAR%;%EXTRACTED_METADATA_JAR%;%EXTRACTED_SERIALIZER_JAR%;%KLIB_JAR%;%UTILITIES_JAR%;%NATIVE_UTILS_JAR%;%UTIL_IO_JAR%;%UTIL_KLIB_JAR%;%TROVE_JAR%;%KLIOPT_JAR%"
set "KONAN_CLASSPATH=%KOTLIN_JAR%;%KOTLIN_STDLIB_JAR%;%KOTLIN_REFLECT_JAR%;%KOTLIN_SCRIPT_RUNTIME_JAR%;%INTEROP_RUNTIME_JAR%;%KONAN_JAR%;%STUB_GENERATOR_JAR%;%INTEROP_INDEXER_JAR%;%SHARED_JAR%;%EXTRACTED_METADATA_JAR%;%EXTRACTED_SERIALIZER_JAR%;%KLIB_JAR%;%UTILITIES_JAR%;%NATIVE_UTILS_JAR%;%UTIL_IO_JAR%;%UTIL_KLIB_JAR%;%TROVE_JAR%;%KOTLINX_CLI_JAR%"
set JAVA_OPTS=-ea ^
-Xmx3G ^
+9 -3
View File
@@ -1,4 +1,10 @@
def endorsedLibrariesList = ['kliopt']
def endorsedLibrariesList = ['kotlinx.cli']
def toTaskName(library) {
def name = ""
library.split("\\.").each { word -> name += word.capitalize() }
return name
}
task clean {
doLast {
@@ -16,11 +22,11 @@ task jvmJar {
targetList.each { target ->
task("${target}EndorsedLibraries", type: Copy) {
endorsedLibrariesList.each { library ->
dependsOn "$library:${target}${library.capitalize()}"
dependsOn "$library:${target}${ toTaskName(library) }"
}
destinationDir project.buildDir
endorsedLibrariesList.each { library ->
from(project("$library").file("build/${target}${library.capitalize()}")) {
from(project("$library").file("build/${target}${ toTaskName(library) }")) {
include('**')
into("$library")
}
@@ -1,863 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kliopt
import kotlin.reflect.KProperty
internal expect fun exitProcess(status: Int): Nothing
/**
* Queue of arguments descriptors.
* Arguments can have several values, so one descriptor can be returned several times.
*/
internal class ArgumentsQueue(argumentsDescriptors: List<ArgParser.ArgDescriptor<*>>) {
/**
* Map of arguments descriptors and their current usage number.
*/
private val argumentsUsageNumber = linkedMapOf(*argumentsDescriptors.map { it to 0 }.toTypedArray())
/**
* Get next descriptor from queue.
*/
fun pop(): String? {
if (argumentsUsageNumber.isEmpty())
return null
val (currentDescriptor, usageNumber) = argumentsUsageNumber.iterator().next()
currentDescriptor.number?.let {
// Parse all arguments for current argument description.
if (usageNumber + 1 >= currentDescriptor.number) {
// All needed arguments were provided.
argumentsUsageNumber.remove(currentDescriptor)
} else {
argumentsUsageNumber[currentDescriptor] = usageNumber + 1
}
}
return currentDescriptor.fullName
}
}
/**
* Abstract base class for subcommands.
*/
@SinceKotlin("1.3")
@ExperimentalCli
abstract class Subcommand(val name: String): ArgParser(name) {
/**
* Execute action if subcommand was provided.
*/
abstract fun execute()
}
/**
* Common descriptor both for options and positional arguments.
*
* @property type option/argument type, one of [ArgType].
* @property fullName option/argument full name.
* @property description text descrition of option/argument.
* @property defaultValue default value for option/argument.
* @property required if option/argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @property deprecatedWarning text message with information in case if option is deprecated.
*/
abstract class Descriptor<T : Any>(val type: ArgType<T>,
val fullName: String,
val description: String? = null,
val defaultValue: List<T> = emptyList(),
val required: Boolean = false,
val deprecatedWarning: String? = null) {
/**
* Text description for help message.
*/
abstract val textDescription: String
/**
* Help message for descriptor.
*/
abstract val helpMessage: String
}
/**
* Argument parsing result.
* Contains name of subcommand which was called.
*
* @property commandName name of command which was called.
*/
class ArgParserResult(val commandName: String)
/**
* Arguments parser.
*
* @property programName name of current program.
* @property useDefaultHelpShortName add or not -h flag for hrlp message.
* @property prefixStyle style of expected options prefix.
* @property skipExtraArguments just skip extra arhuments in command line string without producing error message.
*/
open class ArgParser(val programName: String, var useDefaultHelpShortName: Boolean = true,
var prefixStyle: OPTION_PREFIX_STYLE = OPTION_PREFIX_STYLE.LINUX,
var skipExtraArguments: Boolean = false) {
/**
* Map of options: key - fullname of option, value - pair of descriptor and parsed values.
*/
protected val options = mutableMapOf<String, ParsingValue<*, *>>()
/**
* Map of arguments: key - fullname of argument, value - pair of descriptor and parsed values.
*/
protected val arguments = mutableMapOf<String, ParsingValue<*, *>>()
/**
* Map of subcommands.
*/
@UseExperimental(ExperimentalCli::class)
protected val subcommands = mutableMapOf<String, Subcommand>()
/**
* Mapping for short options names for quick search.
*/
private lateinit var shortNames: Map<String, ParsingValue<*, *>>
/**
* Used prefix form for full option form.
*/
protected val optionFullFormPrefix = if (prefixStyle == OPTION_PREFIX_STYLE.LINUX) "--" else "-"
/**
* Used prefix form for short option form.
*/
protected val optionShortFromPrefix = "-"
/**
* Name with all commands that should be executed.
*/
protected val fullCommandName = mutableListOf<String>(programName)
/**
* Origin of option/argument value.
*
* Possible values:
* SET_BY_USER - value of option was provided in command line string;
* SET_DEFAULT_VALUE - value of option wasn't provided in command line, but set using default value;
* UNSET - value of option is unset
* REDEFINED - value of option was redefined in source code after parsing.
*/
enum class ValueOrigin { SET_BY_USER, SET_DEFAULT_VALUE, UNSET, REDEFINED }
/**
* Options prefix style.
*
* Possible values:
* LINUX - Linux style, for full forms of options "--", for short form - "-"
* JVM - JVM style, both for full and short forms of options "-"
*/
enum class OPTION_PREFIX_STYLE { LINUX, JVM }
/**
* Option descriptor.
*
* Command line entity started with some prefix (-/—) and can have value as next entity in command line string.
*
* @property type option type, one of [ArgType].
* @property fullName option full name.
* @property shortName option short name.
* @property description text descrition of option.
* @property defaultValue default value for option.
* @property required if option is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @property multiple if option can be repeated several times in command line with different values. All values are stored.
* @property delimiter delimiter that separate option provided as one string to several values.
* @property deprecatedWarning text message with information in case if option is deprecated.
*/
inner class OptionDescriptor<T : Any>(
type: ArgType<T>,
fullName: String,
val shortName: String ? = null,
description: String? = null,
defaultValue: List<T> = emptyList(),
required: Boolean = false,
val multiple: Boolean = false,
val delimiter: String? = null,
deprecatedWarning: String? = null) : Descriptor<T> (type, fullName, description, defaultValue,
required, deprecatedWarning) {
override val textDescription: String
get() = "option $optionFullFormPrefix$fullName"
override val helpMessage: String
get() {
val result = StringBuilder()
result.append(" $optionFullFormPrefix$fullName")
shortName?.let { result.append(", $optionShortFromPrefix$it") }
(defaultValue.joinToString(",") { it.toString() }).also { if (!it.isEmpty()) result.append(" [$it]") }
description?.let {result.append(" -> ${it}")}
if (required) result.append(" (always required)")
result.append(" ${type.description}")
deprecatedWarning?.let { result.append(" Warning: $it") }
result.append("\n")
return result.toString()
}
}
/**
* Argument descriptor.
*
* Command line entity which role is connected only with its position.
*
* @property type argument type, one of [ArgType].
* @property fullName argument full name.
* @property number expected number of values. Null means any possible number of values.
* @property description text descrition of argument.
* @property defaultValue default value for argument.
* @property required if argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @property deprecatedWarning text message with information in case if argument is deprecated.
*/
inner class ArgDescriptor<T : Any>(
type: ArgType<T>,
fullName: String,
val number: Int? = null,
description: String? = null,
defaultValue: List<T> = emptyList(),
required: Boolean = true,
deprecatedWarning: String? = null) : Descriptor<T> (type, fullName, description, defaultValue,
required, deprecatedWarning) {
init {
// Check arguments number correctness.
number?.let {
if (it < 0)
printError("Number of arguments for argument description $fullName should be greater than zero.")
}
}
override val textDescription: String
get() = "argument $fullName"
override val helpMessage: String
get() {
val result = StringBuilder()
result.append(" ${fullName}")
(defaultValue.joinToString(",") { it.toString() }).also { if (!it.isEmpty()) result.append(" [$it]") }
description?.let { result.append(" -> ${it}") }
if (!required) result.append(" (optional)")
result.append(" ${type.description}")
deprecatedWarning?.let { result.append(" Warning: $it") }
result.append("\n")
return result.toString()
}
}
/**
* Loader for option with single possible value which is nullable.
*/
inner class SingleNullableOptionLoader<T : Any>(val type: ArgType<T>,
val fullName: String? = null,
val shortName: String ? = null,
val description: String? = null,
val required: Boolean = false,
val deprecatedWarning: String? = null) {
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueInterface<T?> {
val name = fullName ?: prop.name
val descriptor = OptionDescriptor(type, name, shortName, description, emptyList(),
required, deprecatedWarning = deprecatedWarning)
val cliElement = ArgumentSingleNullableValue(type.conversion)
options[name] = ParsingValue(descriptor, cliElement)
return cliElement
}
}
/**
* Loader for option with single possible value which has default value.
*/
inner class SingleOptionWithDefaultLoader<T : Any>(val type: ArgType<T>,
val fullName: String? = null,
val shortName: String ? = null,
val description: String? = null,
val defaultValue: T,
val required: Boolean = false,
val deprecatedWarning: String? = null) {
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueInterface<T> {
val name = fullName ?: prop.name
val descriptor = OptionDescriptor(type, name, shortName, description, listOf(defaultValue),
required, deprecatedWarning = deprecatedWarning)
val cliElement = ArgumentSingleValueWithDefault(type.conversion)
options[name] = ParsingValue(descriptor, cliElement)
return cliElement
}
}
/**
* Loader for option with multiple possible values.
*/
inner class MultipleOptionsLoader<T : Any>(val type: ArgType<T>,
val fullName: String? = null,
val shortName: String ? = null,
val description: String? = null,
val defaultValue: List<T> = emptyList(),
val required: Boolean = false,
val multiple: Boolean = false,
val delimiter: String? = null,
val deprecatedWarning: String? = null) {
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueInterface<MutableList<T>> {
val name = fullName ?: prop.name
val descriptor = OptionDescriptor(type, name, shortName, description, defaultValue,
required, multiple, delimiter, deprecatedWarning)
if (!multiple && delimiter == null)
printError("Several values are expected for option $name. " +
"Option must be used multiple times or split with delimiter.")
val cliElement = ArgumentMultipleValues(type.conversion)
options[name] = ParsingValue(descriptor, cliElement)
return cliElement
}
}
/**
* Add option with single possible value and get delegator to its value.
*
* @param type argument type, one of [ArgType].
* @param fullName argument full name.
* @param shortName option short name.
* @param description text descrition of option.
* @param required if option is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @param deprecatedWarning text message with information in case if option is deprecated.
*/
fun <T : Any>option(type: ArgType<T>,
fullName: String? = null,
shortName: String ? = null,
description: String? = null,
required: Boolean = false,
deprecatedWarning: String? = null) = SingleNullableOptionLoader(type, fullName, shortName,
description, required, deprecatedWarning)
/**
* Add option with single possible value with default and get delegator to its value.
*
* @param type option type, one of [ArgType].
* @param fullName option full name.
* @param shortName option short name.
* @param description text descrition of option.
* @param defaultValue default value for option.
* @param required if option is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @param deprecatedWarning text message with information in case if option is deprecated.
*/
fun <T : Any>option(type: ArgType<T>,
fullName: String? = null,
shortName: String ? = null,
description: String? = null,
defaultValue: T,
required: Boolean = false,
deprecatedWarning: String? = null) = SingleOptionWithDefaultLoader(type, fullName, shortName,
description, defaultValue, required, deprecatedWarning)
/**
* Add option with multiple possible values and get delegator to its values.
*
* @param type option type, one of [ArgType].
* @param fullName option full name.
* @param shortName option short name.
* @param description text descrition of option.
* @param defaultValue default value for option.
* @param required if option is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @param multiple if option can be repeated several times in command line with different values. All values are stored.
* @param delimiter delimiter that separate option provided as one string to several values.
* @param deprecatedWarning text message with information in case if option is deprecated.
*/
fun <T : Any>options(type: ArgType<T>,
fullName: String? = null,
shortName: String ? = null,
description: String? = null,
defaultValue: List<T> = emptyList(),
required: Boolean = false,
multiple: Boolean = false,
delimiter: String? = null,
deprecatedWarning: String? = null) = MultipleOptionsLoader(type, fullName, shortName,
description, defaultValue, required, multiple, delimiter, deprecatedWarning)
/**
* Loader for argument with single possible value which is nullable.
*/
inner class SingleNullableArgumentLoader<T : Any>(val type: ArgType<T>,
val fullName: String? = null,
val description: String? = null,
val required: Boolean = true,
val deprecatedWarning: String? = null) {
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueInterface<T?> {
val name = fullName ?: prop.name
val descriptor = ArgDescriptor(type, name, 1, description,
emptyList(), required, deprecatedWarning)
val cliElement = ArgumentSingleNullableValue(type.conversion)
arguments[name] = ParsingValue(descriptor, cliElement)
return cliElement
}
}
/**
* Loader for argument with single possible value which has default one.
*/
inner class SingleArgumentWithDefaultLoader<T : Any>(val type: ArgType<T>,
val fullName: String? = null,
val description: String? = null,
val defaultValue: T,
val required: Boolean = true,
val deprecatedWarning: String? = null) {
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueInterface<T> {
val name = fullName ?: prop.name
val descriptor = ArgDescriptor(type, name, 1, description,
listOf(defaultValue), required, deprecatedWarning)
val cliElement = ArgumentSingleValueWithDefault(type.conversion)
arguments[name] = ParsingValue(descriptor, cliElement)
return cliElement
}
}
/**
* Loader for option with multiple possible values.
*/
inner class MultipleArgumentsLoader<T : Any>(val type: ArgType<T>,
val fullName: String? = null,
val number: Int? = null,
val description: String? = null,
val defaultValue: List<T> = emptyList(),
val required: Boolean = true,
val deprecatedWarning: String? = null) {
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueInterface<MutableList<T>> {
val name = fullName ?: prop.name
val descriptor = ArgDescriptor(type, name, number, description,
defaultValue, required, deprecatedWarning)
val cliElement = ArgumentMultipleValues(type.conversion)
arguments[name] = ParsingValue(descriptor, cliElement)
return cliElement
}
}
/**
* Add argument with single nullable value and get delegator to its value.
*
* @param type argument type, one of [ArgType].
* @param fullName argument full name.
* @param description text descrition of argument.
* @param required if argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @param deprecatedWarning text message with information in case if argument is deprecated.
*/
fun <T : Any>argument(type: ArgType<T>,
fullName: String? = null,
description: String? = null,
required: Boolean = true,
deprecatedWarning: String? = null) = SingleNullableArgumentLoader(type, fullName, description,
required, deprecatedWarning)
/**
* Add argument with single value with default and get delegator to its value.
*
* @param type argument type, one of [ArgType].
* @param fullName argument full name.
* @param description text descrition of argument.
* @param defaultValue default value for argument.
* @param required if argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @param deprecatedWarning text message with information in case if argument is deprecated.
*/
fun <T : Any>argument(type: ArgType<T>,
fullName: String? = null,
description: String? = null,
defaultValue: T,
required: Boolean = true,
deprecatedWarning: String? = null) = SingleArgumentWithDefaultLoader(type, fullName,
description, defaultValue, required, deprecatedWarning )
/**
* Add argument with [number] possible values and get delegator to its value.
*
* @param type argument type, one of [ArgType].
* @param fullName argument full name.
* @param number expected number of values. Null means any possible number of values.
* @param description text descrition of argument.
* @param defaultValue default value for argument.
* @param required if argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @param deprecatedWarning text message with information in case if argument is deprecated.
*/
fun <T : Any>arguments(type: ArgType<T>,
fullName: String? = null,
number: Int? = null,
description: String? = null,
defaultValue: List<T> = emptyList(),
required: Boolean = true,
deprecatedWarning: String? = null) = MultipleArgumentsLoader(type, fullName, number,
description, defaultValue, required, deprecatedWarning)
/**
* Add subcommands.
*
* @param subcommandsList subcommands to add.
*/
@SinceKotlin("1.3")
@ExperimentalCli
fun subcommands(vararg subcommandsList: Subcommand) {
subcommandsList.forEach {
if (it.name in subcommands) {
printError("Subcommand with name ${it.name} was already defined.")
}
// Set same settings as main parser.
it.prefixStyle = prefixStyle
it.useDefaultHelpShortName = useDefaultHelpShortName
fullCommandName.forEachIndexed { index, namePart ->
it.fullCommandName.add(index, namePart)
}
subcommands[it.name] = it
}
}
/**
* Get all free arguments as unnamed list.
*
* @param type argument type, one of [ArgType].
* @param description text descrition of argument.
* @param defaultValue default value for argument.
* @param required if argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @param deprecatedWarning text message with information in case if argument is deprecated.
*/
fun <T : Any>arguments(type: ArgType<T>,
description: String? = null,
defaultValue: List<T> = emptyList(),
required: Boolean = true,
deprecatedWarning: String? = null): ArgumentValueInterface<MutableList<T>> {
val descriptor = ArgDescriptor(type, "", null, description,
defaultValue, required, deprecatedWarning)
val cliElement = ArgumentMultipleValues(type.conversion)
if ("" in arguments) {
printError("You can have only one unnamed list with positional arguments.")
}
arguments[""] = ParsingValue(descriptor, cliElement)
return cliElement
}
/**
* Parsing value of option/argument.
*/
protected inner class ParsingValue<T: Any, U: Any>(val descriptor: Descriptor<T>, val argumentValue: ArgumentValue<U>) {
/**
* Add parsed value from command line.
*/
fun addValue(stringValue: String,
setValue: ArgumentValue<U>.(String, String) -> Unit = ArgumentValue<U>::addValue) {
// Check of possibility to set several values to one option/argument.
if (descriptor is OptionDescriptor<*> && !descriptor.multiple &&
!argumentValue.isEmpty() && descriptor.delimiter == null) {
printError("Try to provide more than one value for ${descriptor.fullName}.")
}
// Show deprecated warning only first time of using option/argument.
descriptor.deprecatedWarning?.let {
if (argumentValue.isEmpty())
println ("Warning: $it")
}
// Split value if needed.
if (descriptor is OptionDescriptor<*> && descriptor.delimiter != null) {
stringValue.split(descriptor.delimiter).forEach {
argumentValue.setValue(it, descriptor.fullName)
}
} else {
argumentValue.setValue(stringValue, descriptor.fullName)
}
}
/**
* Set default value to option.
*/
fun addDefaultValue() {
descriptor.defaultValue.forEach {
addValue(it.toString(), ArgumentValue<U>::addDefaultValue)
}
if (descriptor.defaultValue.isEmpty() && descriptor.required) {
printError("Please, provide value for ${descriptor.textDescription}. It should be always set.")
}
}
}
/**
* Interface of argument value.
*/
interface ArgumentValueInterface<T> {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T
}
/**
* Argument/option value.
*/
abstract class ArgumentValue<T : Any>(val conversion: (value: String, name: String, helpMessage: String)->T) {
/**
* Values of arguments.
*/
protected lateinit var values: T
/**
* Value origin.
*/
var valueOrigin = ValueOrigin.UNSET
protected set
/**
* Add value from command line.
*
* @param stringValue value from command line.
* @param argumentName name of argument value is added for.
*/
abstract fun addValue(stringValue: String, argumentName: String)
/**
* Add default value.
*
* @param stringValue value from command line.
* @param argumentName name of argument value is added for.
*/
fun addDefaultValue(stringValue: String, argumentName: String) {
addValue(stringValue, argumentName)
valueOrigin = ValueOrigin.SET_DEFAULT_VALUE
}
/**
* Check if values of argument are empty.
*/
abstract fun isEmpty(): Boolean
/**
* Check if value of argument was initialized.
*/
protected fun valuesAreInitialized() = ::values.isInitialized
/**
* Set value from delegated property.
*/
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
values = value
valueOrigin = ValueOrigin.REDEFINED
}
}
/**
* Single argument value.
*
* @property conversion conversion function from string value from command line to expected type.
*/
inner abstract class ArgumentSingleValue<T : Any>(conversion: (value: String, name: String, helpMessage: String)->T):
ArgumentValue<T>(conversion) {
override fun addValue(stringValue: String, argumentName: String) {
if (!valuesAreInitialized()) {
values = conversion(stringValue, argumentName, makeUsage())
valueOrigin = ValueOrigin.SET_BY_USER
} else {
printError("Try to provide more than one value $values and $stringValue for $argumentName.")
}
}
override fun isEmpty(): Boolean = !valuesAreInitialized()
}
/**
* Single nullable argument value.
*
* @property conversion conversion function from string value from command line to expected type.
*/
inner class ArgumentSingleNullableValue<T : Any>(conversion: (value: String, name: String, helpMessage: String)->T):
ArgumentSingleValue<T>(conversion), ArgumentValueInterface<T?> {
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T? = if (!isEmpty()) values else null
}
/**
* Single argument value with default.
*
* @property conversion conversion function from string value from command line to expected type.
*/
inner class ArgumentSingleValueWithDefault<T : Any>(conversion: (value: String, name: String, helpMessage: String)->T):
ArgumentSingleValue<T>(conversion), ArgumentValueInterface<T> {
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T = values
}
/**
* Multiple argument values.
*
* @property conversion conversion function from string value from command line to expected type.
*/
inner class ArgumentMultipleValues<T : Any>(conversion: (value: String, name: String, helpMessage: String)->T):
ArgumentValue<MutableList<T>> (
{ value, name, _ -> mutableListOf(conversion(value, name, makeUsage())) }
), ArgumentValueInterface<MutableList<T>> {
init {
values = mutableListOf()
}
override operator fun getValue(thisRef: Any?, property: KProperty<*>): MutableList<T> = values
override fun addValue(stringValue: String, argumentName: String) {
values.addAll(conversion(stringValue, argumentName, makeUsage()))
valueOrigin = ValueOrigin.SET_BY_USER
}
override fun isEmpty() = values.isEmpty()
}
/**
* Output error. Also adds help usage information for easy understanding of problem.
*
* @param message error message.
*/
fun printError(message: String): Nothing {
error("$message\n${makeUsage()}")
}
/**
* Get origin of option value.
*
* @param name name of argument/option.
*/
fun getOrigin(name: String) = options[name]?.argumentValue?.valueOrigin ?:
arguments[name]?.argumentValue?.valueOrigin ?: printError("No option/argument $name in list of avaliable options")
/**
* Save value as argument value.
*
* @param arg string with argument value.
* @param argumentsQueue queue with active argument descriptors.
*/
private fun saveAsArg(arg: String, argumentsQueue: ArgumentsQueue): Boolean {
// Find next uninitialized arguments.
val name = argumentsQueue.pop()
name?.let {
val argumentValue = arguments[name]!!
argumentValue.descriptor.deprecatedWarning?.let { println ("Warning: $it") }
argumentValue.addValue(arg)
return true
}
return false
}
/**
* Save value as option value.
*/
private fun <T : Any, U: Any> saveAsOption(parsingValue: ParsingValue<T, U>, value: String) {
parsingValue.addValue(value)
}
/**
* Try to recognize command line element as full form of option.
*
* @param candidate string with candidate in options.
*/
protected fun recognizeOptionFullForm(candidate: String) =
if (candidate.startsWith(optionFullFormPrefix))
options[candidate.substring(optionFullFormPrefix.length)]
else null
/**
* Try to recognize command line element as short form of option.
*
* @param candidate string with candidate in options.
*/
protected fun recognizeOptionShortForm(candidate: String) =
if (candidate.startsWith(optionShortFromPrefix))
shortNames[candidate.substring(optionShortFromPrefix.length)]
else null
/**
* Parse arguments.
*
* @param args array with command line arguments.
*
* @return true if all arguments were parsed successfully, otherwise return false and print help message.
*/
fun parse(args: Array<String>): ArgParserResult {
// Add help option.
val helpDescriptor = if (useDefaultHelpShortName) OptionDescriptor(ArgType.Boolean,
"help", "h", "Usage info")
else OptionDescriptor(ArgType.Boolean, "help", description = "Usage info")
options["help"] = ParsingValue(helpDescriptor, ArgumentSingleNullableValue(helpDescriptor.type.conversion))
// Add default list with arguments if there can be extra free arguments.
if (skipExtraArguments) {
arguments(ArgType.String)
}
val argumentsQueue = ArgumentsQueue(arguments.map { it.value.descriptor as ArgDescriptor<*> })
// Fill map with short names of options.
shortNames = options.filter { (it.value.descriptor as? OptionDescriptor<*>)?.shortName != null }.
map { (it.value.descriptor as OptionDescriptor<*>).shortName!! to it.value }.toMap()
var index = 0
while (index < args.size) {
val arg = args[index]
// Check for subcommands.
@UseExperimental(ExperimentalCli::class)
subcommands.forEach { (name, subcommand) ->
if (arg == name) {
// Use parser for this subcommand.
subcommand.parse(args.slice(index + 1..args.size - 1).toTypedArray())
subcommand.execute()
return ArgParserResult(name)
}
}
// Parse argumnets from command line.
if (arg.startsWith('-')) {
// Candidate in being option.
// Option is found.
val argValue = recognizeOptionShortForm(arg) ?: recognizeOptionFullForm(arg)
argValue?.descriptor?.let {
if (argValue.descriptor.type.hasParameter) {
if (index < args.size - 1) {
saveAsOption(argValue, args[index + 1])
index++
} else {
// An error, option with value without value.
printError("No value for ${argValue.descriptor.textDescription}")
}
} else {
// Boolean flags.
if (argValue.descriptor.fullName == "help") {
println(makeUsage())
exitProcess(0)
}
saveAsOption(argValue, "true")
}
} ?: run {
// Try save as argument.
if (!saveAsArg(arg, argumentsQueue)) {
printError("Unknown option $arg")
}
}
} else {
// Argument is found.
if (!saveAsArg(arg, argumentsQueue)) {
printError("Too many arguments! Couldn't proccess argument $arg!")
}
}
index++
}
// Postprocess results of parsing.
options.values.union(arguments.values).forEach { value ->
// Not inited, append default value if needed.
if (value.argumentValue.isEmpty()) {
value.addDefaultValue()
}
}
return ArgParserResult(programName)
}
/**
* Create message with usage description.
*/
internal fun makeUsage(): String {
val result = StringBuilder()
result.append("Usage: ${fullCommandName.joinToString(" ")} options_list\n")
if (!arguments.isEmpty()) {
result.append("Arguments: \n")
arguments.forEach {
result.append(it.value.descriptor.helpMessage)
}
}
result.append("Options: \n")
options.forEach {
result.append(it.value.descriptor.helpMessage)
}
return result.toString()
}
}
@@ -60,7 +60,7 @@ kotlin {
jvm().compilations.all {
kotlinOptions {
freeCompilerArgs = ["-Xuse-experimental=org.jetbrains.kliopt.ExperimentalCli"]
freeCompilerArgs = ["-Xuse-experimental=kotlinx.cli.ExperimentalCli"]
suppressWarnings = true
}
}
@@ -76,23 +76,23 @@ targetList.each { target ->
"-Djava.library.path=${project.buildDir}/nativelibs/$hostName",
]
def defaultArgs = ['-nopack', '-nodefaultlibs']
def defaultArgs = ['-nopack', '-no-default-libs', '-no-endorsed-libs']
if (target != "wasm32") defaultArgs += '-g'
def konanArgs = [*defaultArgs,
'-target', target,
"-Xruntime=${project(':runtime').file('build/' + target + '/runtime.bc')}",
*project.globalBuildArgs]
task("${target}Kliopt", type: JavaExec) {
task("${target}KotlinxCli", type: JavaExec) {
dependsOn ":${target}CrossDistRuntime"
main = 'org.jetbrains.kotlin.cli.bc.K2NativeKt'
classpath = project(":backend.native").configurations.cli_bc
jvmArgs = konanJvmArgs
args = [*konanArgs,
'-output', project.file("build/${target}Kliopt"),
'-produce', 'library', '-module-name', 'kliopt', '-XXLanguage:+AllowContractsForCustomFunctions',
'-Xmulti-platform', '-Xuse-experimental=org.jetbrains.kliopt.ExperimentalCli',
'-output', project.file("build/${target}KotlinxCli"),
'-produce', 'library', '-module-name', 'kotlinx-cli', '-XXLanguage:+AllowContractsForCustomFunctions',
'-Xmulti-platform', '-Xuse-experimental=kotlinx.cli.ExperimentalCli',
'-Xuse-experimental=kotlin.ExperimentalMultiplatform',
'-Xallow-result-return-type',
commonSrc.absolutePath,
@@ -100,6 +100,6 @@ targetList.each { target ->
nativeSrc]
inputs.dir(nativeSrc)
inputs.dir(commonSrc)
outputs.dir(project.file("build/${target}Kliopt"))
outputs.dir(project.file("build/${target}KotlinxCli"))
}
}
@@ -3,7 +3,7 @@
* that can be found in the LICENSE file.
*/
package org.jetbrains.kliopt
package kotlinx.cli
internal actual fun exitProcess(status: Int): Nothing {
error("Not implemented for JS!")
@@ -3,7 +3,7 @@
* that can be found in the LICENSE file.
*/
package org.jetbrains.kliopt
package kotlinx.cli
internal actual fun exitProcess(status: Int): Nothing {
kotlin.system.exitProcess(0)
@@ -3,7 +3,7 @@
* that can be found in the LICENSE file.
*/
package org.jetbrains.kliopt
package kotlinx.cli
internal actual fun exitProcess(status: Int): Nothing {
kotlin.system.exitProcess(0)
@@ -0,0 +1,449 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlinx.cli
import kotlin.reflect.KProperty
internal expect fun exitProcess(status: Int): Nothing
/**
* Queue of arguments descriptors.
* Arguments can have several values, so one descriptor can be returned several times.
*/
internal class ArgumentsQueue(argumentsDescriptors: List<ArgDescriptor<*, *>>) {
/**
* Map of arguments descriptors and their current usage number.
*/
private val argumentsUsageNumber = linkedMapOf(*argumentsDescriptors.map { it to 0 }.toTypedArray())
/**
* Get next descriptor from queue.
*/
fun pop(): String? {
if (argumentsUsageNumber.isEmpty())
return null
val (currentDescriptor, usageNumber) = argumentsUsageNumber.iterator().next()
currentDescriptor.number?.let {
// Parse all arguments for current argument description.
if (usageNumber + 1 >= currentDescriptor.number) {
// All needed arguments were provided.
argumentsUsageNumber.remove(currentDescriptor)
} else {
argumentsUsageNumber[currentDescriptor] = usageNumber + 1
}
}
return currentDescriptor.fullName
}
}
/**
* Interface of argument value.
*/
interface ArgumentValueDelegate<T> {
var value: T
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
/**
* Abstract base class for subcommands.
*/
@SinceKotlin("1.3")
@ExperimentalCli
abstract class Subcommand(val name: String): ArgParser(name) {
/**
* Execute action if subcommand was provided.
*/
abstract fun execute()
}
/**
* Argument parsing result.
* Contains name of subcommand which was called.
*
* @property commandName name of command which was called.
*/
class ArgParserResult(val commandName: String)
/**
* Arguments parser.
*
* @property programName name of current program.
* @property useDefaultHelpShortName add or not -h flag for help message.
* @property prefixStyle style of expected options prefix.
* @property skipExtraArguments just skip extra arguments in command line string without producing error message.
*/
open class ArgParser(val programName: String, var useDefaultHelpShortName: Boolean = true,
var prefixStyle: OPTION_PREFIX_STYLE = OPTION_PREFIX_STYLE.LINUX,
var skipExtraArguments: Boolean = false) {
/**
* Map of options: key - full name of option, value - pair of descriptor and parsed values.
*/
private val options = mutableMapOf<String, ParsingValue<*, *>>()
/**
* Map of arguments: key - full name of argument, value - pair of descriptor and parsed values.
*/
private val arguments = mutableMapOf<String, ParsingValue<*, *>>()
/**
* Map with declared options.
*/
private val declaredOptions = mutableListOf<CLIEntityWrapper>()
/**
* Map with declared arguments.
*/
private val declaredArguments = mutableListOf<CLIEntityWrapper>()
/**
* Map of subcommands.
*/
@UseExperimental(ExperimentalCli::class)
protected val subcommands = mutableMapOf<String, Subcommand>()
/**
* Mapping for short options names for quick search.
*/
private val shortNames = mutableMapOf<String, ParsingValue<*, *>>()
/**
* Used prefix form for full option form.
*/
private val optionFullFormPrefix = if (prefixStyle == OPTION_PREFIX_STYLE.LINUX) "--" else "-"
/**
* Used prefix form for short option form.
*/
private val optionShortFromPrefix = "-"
/**
* Name with all commands that should be executed.
*/
protected val fullCommandName = mutableListOf(programName)
/**
* Origin of option/argument value.
*
* Possible values:
* SET_BY_USER - value of option was provided in command line string;
* SET_DEFAULT_VALUE - value of option wasn't provided in command line, but set using default value;
* UNSET - value of option is unset
* REDEFINED - value of option was redefined in source code after parsing.
*/
enum class ValueOrigin { SET_BY_USER, SET_DEFAULT_VALUE, UNSET, REDEFINED }
/**
* Options prefix style.
*
* Possible values:
* LINUX - Linux style, for full forms of options "--", for short form - "-"
* JVM - JVM style, both for full and short forms of options "-"
*/
enum class OPTION_PREFIX_STYLE { LINUX, JVM }
/**
* Add option with single possible value and get delegator to its value.
*
* @param type argument type, one of [ArgType].
* @param fullName argument full name.
* @param shortName option short name.
* @param description text description of Argument.
* @param deprecatedWarning text message with information in case if option is deprecated.
*/
fun <T : Any>option(type: ArgType<T>,
fullName: String? = null,
shortName: String ? = null,
description: String? = null,
deprecatedWarning: String? = null): SingleNullableOption<T> {
val option = SingleNullableOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type,
fullName, shortName, description, deprecatedWarning = deprecatedWarning), CLIEntityWrapper())
option.owner.entity = option
declaredOptions.add(option.owner)
return option
}
/**
* Check usage of required property for arguments.
* Make sense only for several last arguments.
*/
private fun inspectRequiredAndDefaultUsage() {
var previousArgument: ParsingValue<*, *>? = null
arguments.forEach { (_, currentArgument) ->
previousArgument?.let {
// Previous argument has default value.
it.descriptor.defaultValue?.let {
if (currentArgument.descriptor.defaultValue == null && currentArgument.descriptor.required) {
printWarning("Default value of argument ${previousArgument.descriptor.fullName} will be unused, " +
"because next argument ${currentArgument.descriptor.fullName} is always required and has no default value.")
}
}
// Previous argument is optional.
if (!it.descriptor.required) {
if (currentArgument.descriptor.defaultValue == null && currentArgument.descriptor.required) {
printWarning("Argument ${previousArgument.descriptor.fullName} will be always required, " +
"because next argument ${currentArgument.descriptor.fullName} is always required.")
}
}
}
}
}
/**
* Add argument with single nullable value and get delegator to its value.
*
* @param type argument type, one of [ArgType].
* @param fullName argument full name.
* @param description text description of argument.
* @param deprecatedWarning text message with information in case if argument is deprecated.
*/
fun <T : Any>argument(type: ArgType<T>,
fullName: String? = null,
description: String? = null,
deprecatedWarning: String? = null) : SingleArgument<T> {
val argument = SingleArgument(ArgDescriptor(type, fullName, 1,
description, deprecatedWarning = deprecatedWarning), CLIEntityWrapper())
argument.owner.entity = argument
declaredArguments.add(argument.owner)
return argument
}
/**
* Add subcommands.
*
* @param subcommandsList subcommands to add.
*/
@SinceKotlin("1.3")
@ExperimentalCli
fun subcommands(vararg subcommandsList: Subcommand) {
subcommandsList.forEach {
if (it.name in subcommands) {
printError("Subcommand with name ${it.name} was already defined.")
}
// Set same settings as main parser.
it.prefixStyle = prefixStyle
it.useDefaultHelpShortName = useDefaultHelpShortName
fullCommandName.forEachIndexed { index, namePart ->
it.fullCommandName.add(index, namePart)
}
subcommands[it.name] = it
}
}
/**
* Output error. Also adds help usage information for easy understanding of problem.
*
* @param message error message.
*/
fun printError(message: String): Nothing {
error("$message\n${makeUsage()}")
}
/**
* Save value as argument value.
*
* @param arg string with argument value.
* @param argumentsQueue queue with active argument descriptors.
*/
private fun saveAsArg(arg: String, argumentsQueue: ArgumentsQueue): Boolean {
// Find next uninitialized arguments.
val name = argumentsQueue.pop()
name?.let {
val argumentValue = arguments[name]!!
argumentValue.descriptor.deprecatedWarning?.let { printWarning(it) }
argumentValue.addValue(arg)
return true
}
return false
}
/**
* Save value as option value.
*/
private fun <T : Any, U: Any> saveAsOption(parsingValue: ParsingValue<T, U>, value: String) {
parsingValue.addValue(value)
}
/**
* Try to recognize command line element as full form of option.
*
* @param candidate string with candidate in options.
*/
private fun recognizeOptionFullForm(candidate: String) =
if (candidate.startsWith(optionFullFormPrefix))
options[candidate.substring(optionFullFormPrefix.length)]
else null
/**
* Try to recognize command line element as short form of option.
*
* @param candidate string with candidate in options.
*/
private fun recognizeOptionShortForm(candidate: String) =
if (candidate.startsWith(optionShortFromPrefix))
shortNames[candidate.substring(optionShortFromPrefix.length)]
else null
/**
* Parse arguments.
*
* @param args array with command line arguments.
*
* @return true if all arguments were parsed successfully, otherwise return false and print help message.
*/
fun parse(args: Array<String>) = parse(args.asList())
protected fun parse(args: List<String>): ArgParserResult {
// Add help option.
val helpDescriptor = if (useDefaultHelpShortName) OptionDescriptor<Boolean, Boolean>(optionFullFormPrefix,
optionShortFromPrefix, ArgType.Boolean,
"help", "h", "Usage info")
else OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix,
ArgType.Boolean, "help", description = "Usage info")
val helpOption = SingleNullableOption(helpDescriptor, CLIEntityWrapper())
helpOption.owner.entity = helpOption
declaredOptions.add(helpOption.owner)
// Add default list with arguments if there can be extra free arguments.
if (skipExtraArguments) {
argument(ArgType.String, "").number()
}
// Map declared options and arguments to maps.
declaredOptions.forEachIndexed { index, option ->
val value = option.entity?.delegate as ParsingValue<*, *>
value.descriptor.fullName?.let {
// Add option.
if (options.containsKey(it)) {
error("Option with full name $it was already added.")
}
with(value.descriptor as OptionDescriptor) {
if (shortName != null && shortNames.containsKey(shortName)) {
error("Option with short name ${shortName} was already added.")
}
shortName?.let {
shortNames[it] = value
}
}
options[it] = value
} ?: error("Option was added, but unnamed. Added option under №${index + 1}")
}
declaredArguments.forEachIndexed { index, argument ->
val value = argument.entity?.delegate as ParsingValue<*, *>
value.descriptor.fullName?.let {
// Add option.
if (arguments.containsKey(it)) {
error("Argument with full name $it was already added.")
}
arguments[it] = value
} ?: error("Argument was added, but unnamed. Added argument under №${index + 1}")
}
// Make inspections for arguments.
inspectRequiredAndDefaultUsage()
val argumentsQueue = ArgumentsQueue(arguments.map { it.value.descriptor as ArgDescriptor<*, *> })
var index = 0
try {
while (index < args.size) {
val arg = args[index]
// Check for subcommands.
@UseExperimental(ExperimentalCli::class)
subcommands.forEach { (name, subcommand) ->
if (arg == name) {
// Use parser for this subcommand.
subcommand.parse(args.slice(index + 1..args.size - 1))
subcommand.execute()
return ArgParserResult(name)
}
}
// Parse arguments from command line.
if (arg.startsWith('-')) {
// Candidate in being option.
// Option is found.
val argValue = recognizeOptionShortForm(arg) ?: recognizeOptionFullForm(arg)
argValue?.descriptor?.let {
if (argValue.descriptor.type.hasParameter) {
if (index < args.size - 1) {
saveAsOption(argValue, args[index + 1])
index++
} else {
// An error, option with value without value.
printError("No value for ${argValue.descriptor.textDescription}")
}
} else {
// Boolean flags.
if (argValue.descriptor.fullName == "help") {
println(makeUsage())
exitProcess(0)
}
saveAsOption(argValue, "true")
}
} ?: run {
// Try save as argument.
if (!saveAsArg(arg, argumentsQueue)) {
printError("Unknown option $arg")
}
}
} else {
// Argument is found.
if (!saveAsArg(arg, argumentsQueue)) {
printError("Too many arguments! Couldn't process argument $arg!")
}
}
index++
}
// Postprocess results of parsing.
options.values.union(arguments.values).forEach { value ->
// Not inited, append default value if needed.
if (value.isEmpty()) {
value.addDefaultValue()
}
}
} catch (exception: ParsingException) {
printError(exception.message!!)
}
return ArgParserResult(programName)
}
/**
* Create message with usage description.
*/
internal fun makeUsage(): String {
val result = StringBuilder()
result.append("Usage: ${fullCommandName.joinToString(" ")} options_list\n")
if (arguments.isNotEmpty()) {
result.append("Arguments: \n")
arguments.forEach {
result.append(it.value.descriptor.helpMessage)
}
}
if (options.isNotEmpty()) {
result.append("Options: \n")
options.forEach {
result.append(it.value.descriptor.helpMessage)
}
}
return result.toString()
}
}
/**
* Output warning.
*
* @param message warning message.
*/
internal fun printWarning(message: String) {
println("WARNING $message")
}
@@ -3,7 +3,7 @@
* that can be found in the LICENSE file.
*/
package org.jetbrains.kliopt
package kotlinx.cli
/**
* Possible types of arguments.
@@ -23,7 +23,7 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
*
* @param value value
*/
abstract val conversion: (value: kotlin.String, name: kotlin.String, helpMessage: kotlin.String)->T
abstract val conversion: (value: kotlin.String, name: kotlin.String)->T
/**
* Argument type for flags that can be only set/unset.
@@ -32,8 +32,8 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
override val description: kotlin.String
get() = ""
override val conversion: (value: kotlin.String, name: kotlin.String, _: kotlin.String) -> kotlin.Boolean
get() = { value, _ , _ -> if (value == "false") false else true }
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.Boolean
get() = { value, _ -> if (value == "false") false else true }
}
/**
@@ -43,8 +43,8 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
override val description: kotlin.String
get() = "{ String }"
override val conversion: (value: kotlin.String, name: kotlin.String, _: kotlin.String) -> kotlin.String
get() = { value, _, _ -> value }
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.String
get() = { value, _ -> value }
}
/**
@@ -54,9 +54,9 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
override val description: kotlin.String
get() = "{ Int }"
override val conversion: (value: kotlin.String, name: kotlin.String, helpMessage: kotlin.String) -> kotlin.Int
get() = { value, name, helpMessage -> value.toIntOrNull()
?: error("Option $name is expected to be integer number. $value is provided.\n$helpMessage") }
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.Int
get() = { value, name -> value.toIntOrNull()
?: throw ParsingException("Option $name is expected to be integer number. $value is provided.") }
}
/**
@@ -66,10 +66,9 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
override val description: kotlin.String
get() = "{ Double }"
override val conversion: (value: kotlin.String, name: kotlin.String,
helpMessage: kotlin.String) -> kotlin.Double
get() = { value, name, helpMessage -> value.toDoubleOrNull()
?: error("Option $name is expected to be double number. $value is provided.\n$helpMessage") }
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.Double
get() = { value, name -> value.toDoubleOrNull()
?: throw ParsingException("Option $name is expected to be double number. $value is provided.") }
}
/**
@@ -79,9 +78,10 @@ abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
override val description: kotlin.String
get() = "{ Value should be one of $values }"
override val conversion: (value: kotlin.String, name: kotlin.String,
helpMessage: kotlin.String) -> kotlin.String
get() = { value, name, helpMessage -> if (value in values) value
else error("Option $name is expected to be one of $values. $value is provided.\n$helpMessage") }
override val conversion: (value: kotlin.String, name: kotlin.String) -> kotlin.String
get() = { value, name -> if (value in values) value
else throw ParsingException("Option $name is expected to be one of $values. $value is provided.") }
}
}
}
internal class ParsingException(message: String) : Exception(message)
@@ -0,0 +1,172 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlinx.cli
import kotlin.reflect.KProperty
/**
* Parsing value of option/argument.
*/
internal abstract class ParsingValue<T: Any, TResult: Any>(val descriptor: Descriptor<T, TResult>) {
/**
* Values of arguments.
*/
protected lateinit var parsedValue: TResult
/**
* Value origin.
*/
var valueOrigin = ArgParser.ValueOrigin.UNSET
protected set
/**
* Check if values of argument are empty.
*/
abstract fun isEmpty(): Boolean
/**
* Check if value of argument was initialized.
*/
protected fun valueIsInitialized() = ::parsedValue.isInitialized
/**
* Sace value from command line.
*
* @param stringValue value from command line.
*/
protected abstract fun saveValue(stringValue: String)
/**
* Set value of delegated property.
*/
fun setDelegatedValue(providedValue: TResult) {
parsedValue = providedValue
valueOrigin = ArgParser.ValueOrigin.REDEFINED
}
/**
* Add parsed value from command line.
*
* @param stringValue value from command line.
*/
internal fun addValue(stringValue: String) {
// Check of possibility to set several values to one option/argument.
if (descriptor is OptionDescriptor<*, *> && !descriptor.multiple &&
!isEmpty() && descriptor.delimiter == null) {
throw ParsingException("Try to provide more than one value for ${descriptor.fullName}.")
}
// Show deprecated warning only first time of using option/argument.
descriptor.deprecatedWarning?.let {
if (isEmpty())
println ("Warning: $it")
}
// Split value if needed.
if (descriptor is OptionDescriptor<*, *> && descriptor.delimiter != null) {
stringValue.split(descriptor.delimiter).forEach {
saveValue(it)
}
} else {
saveValue(stringValue)
}
}
/**
* Set default value to option.
*/
fun addDefaultValue() {
if (!descriptor.defaultValueSet && descriptor.required) {
throw ParsingException("Please, provide value for ${descriptor.textDescription}. It should be always set.")
}
if (descriptor.defaultValueSet) {
parsedValue = descriptor.defaultValue!!
valueOrigin = ArgParser.ValueOrigin.SET_DEFAULT_VALUE
}
}
/**
* Provide name for CLI entity.
*
* @param name name for CLI entity.
*/
fun provideName(name: String) {
descriptor.fullName ?: run { descriptor.fullName = name }
}
}
/**
* Single argument value.
*
* @property descriptor descriptor of option/argument.
*/
internal abstract class AbstractArgumentSingleValue<T: Any>(descriptor: Descriptor<T, T>):
ParsingValue<T, T>(descriptor) {
override fun saveValue(stringValue: String) {
if (!valueIsInitialized()) {
parsedValue = descriptor.type.conversion(stringValue, descriptor.fullName!!)
valueOrigin = ArgParser.ValueOrigin.SET_BY_USER
} else {
throw ParsingException("Try to provide more than one value $parsedValue and $stringValue for ${descriptor.fullName}.")
}
}
override fun isEmpty(): Boolean = !valueIsInitialized()
}
/**
* Single argument value.
*
* @property descriptor descriptor of option/argument.
*/
internal class ArgumentSingleValue<T: Any>(descriptor: Descriptor<T, T>): AbstractArgumentSingleValue<T>(descriptor),
ArgumentValueDelegate<T> {
override var value: T
get() = parsedValue
set(value) = setDelegatedValue(value)
}
/**
* Single nullable argument value.
*
* @property descriptor descriptor of option/argument.
*/
internal class ArgumentSingleNullableValue<T : Any>(descriptor: Descriptor<T, T>):
AbstractArgumentSingleValue<T>(descriptor), ArgumentValueDelegate<T?> {
private var setToNull = false
override var value: T?
get() = if (!isEmpty() && !setToNull) parsedValue else null
set(providedValue) = providedValue?.let {
setDelegatedValue(it)
setToNull = false
} ?: run {
setToNull = true
valueOrigin = ArgParser.ValueOrigin.REDEFINED
}
}
/**
* Multiple argument values.
*
* @property descriptor descriptor of option/argument.
*/
internal class ArgumentMultipleValues<T : Any>(descriptor: Descriptor<T, List<T>>):
ParsingValue<T, List<T>>(descriptor), ArgumentValueDelegate<List<T>> {
private val addedValue = mutableListOf<T>()
init {
parsedValue = addedValue
}
override var value: List<T>
get() = parsedValue
set(value) = setDelegatedValue(value)
override fun saveValue(stringValue: String) {
addedValue.add(descriptor.type.conversion(stringValue, descriptor.fullName!!))
valueOrigin = ArgParser.ValueOrigin.SET_BY_USER
}
override fun isEmpty() = parsedValue.isEmpty()
}
@@ -0,0 +1,165 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlinx.cli
import kotlin.reflect.KProperty
internal data class CLIEntityWrapper(var entity: CLIEntity<*>? = null)
/**
* Command line entity.
*
* @property owner parser which owns current entity.
*/
abstract class CLIEntity<TResult> internal constructor(internal val owner: CLIEntityWrapper) {
/**
* Wrapper for element - read only property.
* Needed to close set of variable [cliElement].
*/
lateinit var delegate: ArgumentValueDelegate<TResult>
internal set
/**
* Value of entity.
*/
var value: TResult
get() = delegate.value
set(value) { delegate.value = value }
/**
* Origin of argument value.
*/
val valueOrigin: ArgParser.ValueOrigin
get() = (delegate as ParsingValue<*, *>).valueOrigin
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueDelegate<TResult> {
(delegate as ParsingValue<*, *>).provideName(prop.name)
return delegate
}
}
/**
* Argument instance.
*/
abstract class Argument<TResult> internal constructor(owner: CLIEntityWrapper): CLIEntity<TResult>(owner)
/**
* Common single argument instance.
*/
abstract class AbstractSingleArgument<T: Any, TResult> internal constructor(owner: CLIEntityWrapper): Argument<TResult>(owner) {
/**
* Check descriptor for this kind of argument.
*/
internal fun checkDescriptor(descriptor: ArgDescriptor<*, *>) {
if (descriptor.number == null || descriptor.number > 1) {
failAssertion("Argument with single value can't be initialized with descriptor for multiple values.")
}
}
}
/**
* Argument with single non-nullable value.
*/
class SingleArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T, T>, owner: CLIEntityWrapper):
AbstractSingleArgument<T, T>(owner) {
init {
checkDescriptor(descriptor)
delegate = ArgumentSingleValue(descriptor)
}
}
/**
* Argument with single nullable value.
*/
class SingleNullableArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T, T>, owner: CLIEntityWrapper):
AbstractSingleArgument<T, T?>(owner){
init {
checkDescriptor(descriptor)
delegate = ArgumentSingleNullableValue(descriptor)
}
}
/**
* Argument with multiple values.
*/
class MultipleArgument<T : Any> internal constructor(descriptor: ArgDescriptor<T, List<T>>, owner: CLIEntityWrapper):
Argument<List<T>>(owner) {
init {
if (descriptor.number != null && descriptor.number == 1) {
failAssertion("Argument with multiple values can't be initialized with descriptor for single one.")
}
delegate = ArgumentMultipleValues(descriptor)
}
}
/**
* Allow argument have several values.
*
* @param number number of arguments are expected. In case of null value any number of arguments can be set.
*/
fun <T : Any, TResult> AbstractSingleArgument<T, TResult>.number(value: Int? = null): MultipleArgument<T> {
if (value != null && value == 1) {
error("number() modifier with value 1 is unavailable. It's already set to 1.")
}
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
MultipleArgument(ArgDescriptor(type, fullName, value, description, listOfNotNull(defaultValue),
required, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
}
/**
* Set default value for argument.
*
* @param value default value.
*/
fun <T: Any, TResult> AbstractSingleArgument<T, TResult>.default(value: T): SingleArgument<T> {
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
SingleArgument(ArgDescriptor(type, fullName, number, description, value, required, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
}
/**
* Set default value for argument.
*
* @param value default value.
*/
fun <T: Any> MultipleArgument<T>.default(value: Collection<T>): MultipleArgument<T> {
val newArgument = with((delegate as ParsingValue<T, List<T>>).descriptor as ArgDescriptor) {
MultipleArgument(ArgDescriptor(type, fullName, number, description, value.toList(),
required, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
}
/**
* Allow argument be unprovided in command line.
*/
fun <T: Any, TResult> AbstractSingleArgument<T, TResult>.optional(): SingleNullableArgument<T> {
val newArgument = with((delegate as ParsingValue<T, T>).descriptor as ArgDescriptor) {
SingleNullableArgument(ArgDescriptor(type, fullName, number, description, defaultValue,
false, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
}
/**
* Allow argument be unprovided in command line.
*/
fun <T: Any> MultipleArgument<T>.optional(): MultipleArgument<T> {
val newArgument = with((delegate as ParsingValue<T, List<T>>).descriptor as ArgDescriptor) {
MultipleArgument(ArgDescriptor(type, fullName, number, description,
defaultValue?.toList() ?: listOf(), false, deprecatedWarning), owner)
}
owner.entity = newArgument
return newArgument
}
fun failAssertion(message: String): Nothing = throw AssertionError(message)
@@ -0,0 +1,152 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlinx.cli
/**
* Common descriptor both for options and positional arguments.
*
* @property type option/argument type, one of [ArgType].
* @property fullName option/argument full name.
* @property description text description of option/argument.
* @property defaultValue default value for option/argument.
* @property required if option/argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @property deprecatedWarning text message with information in case if option is deprecated.
*/
internal abstract class Descriptor<T : Any, TResult>(val type: ArgType<T>,
var fullName: String? = null,
val description: String? = null,
val defaultValue: TResult? = null,
val required: Boolean = false,
val deprecatedWarning: String? = null) {
/**
* Text description for help message.
*/
abstract val textDescription: String
/**
* Help message for descriptor.
*/
abstract val helpMessage: String
/**
* Provide text description of value.
*
* @param value value got getting text description for.
*/
fun valueDescription(value: TResult?) = value?.let {
if (it is List<*> && it.isNotEmpty())
" [${it.joinToString { it.toString() }}]"
else if (it !is List<*>)
" [$it]"
else null
}
/**
* Flag to check if descriptor has set default value for option/argument.
*/
val defaultValueSet by lazy {
defaultValue != null && (defaultValue is List<*> && defaultValue.isNotEmpty() || defaultValue !is List<*>)
}
}
/**
* Option descriptor.
*
* Command line entity started with some prefix (-/--) and can have value as next entity in command line string.
*
* @property optionFullFormPrefix prefix used before full form of option.
* @property optionShortFromPrefix prefix used before short form of option.
* @property type option type, one of [ArgType].
* @property fullName option full name.
* @property shortName option short name.
* @property description text description of option.
* @property defaultValue default value for option.
* @property required if option is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @property multiple if option can be repeated several times in command line with different values. All values are stored.
* @property delimiter delimiter that separate option provided as one string to several values.
* @property deprecatedWarning text message with information in case if option is deprecated.
*/
internal class OptionDescriptor<T : Any, TResult>(
val optionFullFormPrefix: String,
val optionShortFromPrefix: String,
type: ArgType<T>,
fullName: String? = null,
val shortName: String ? = null,
description: String? = null,
defaultValue: TResult? = null,
required: Boolean = false,
val multiple: Boolean = false,
val delimiter: String? = null,
deprecatedWarning: String? = null) : Descriptor<T, TResult>(type, fullName, description, defaultValue,
required, deprecatedWarning) {
override val textDescription: String
get() = "option $optionFullFormPrefix$fullName"
override val helpMessage: String
get() {
val result = StringBuilder()
result.append(" $optionFullFormPrefix$fullName")
shortName?.let { result.append(", $optionShortFromPrefix$it") }
valueDescription(defaultValue)?.let {
result.append("$it")
}
description?.let {result.append(" -> ${it}")}
if (required) result.append(" (always required)")
result.append(" ${type.description}")
deprecatedWarning?.let { result.append(" Warning: $it") }
result.append("\n")
return result.toString()
}
}
/**
* Argument descriptor.
*
* Command line entity which role is connected only with its position.
*
* @property type argument type, one of [ArgType].
* @property fullName argument full name.
* @property number expected number of values. Null means any possible number of values.
* @property description text description of argument.
* @property defaultValue default value for argument.
* @property required if argument is required or not. If it's required and not provided in command line and have no default value, error will be generated.
* @property deprecatedWarning text message with information in case if argument is deprecated.
*/
internal class ArgDescriptor<T : Any, TResult>(
type: ArgType<T>,
fullName: String?,
val number: Int? = null,
description: String? = null,
defaultValue: TResult? = null,
required: Boolean = true,
deprecatedWarning: String? = null) : Descriptor<T, TResult>(type, fullName, description, defaultValue,
required, deprecatedWarning) {
init {
// Check arguments number correctness.
number?.let {
if (it < 0)
error("Number of arguments for argument description $fullName should be greater than zero.")
}
}
override val textDescription: String
get() = "argument $fullName"
override val helpMessage: String
get() {
val result = StringBuilder()
result.append(" ${fullName}")
valueDescription(defaultValue)?.let {
result.append("$it")
}
description?.let { result.append(" -> ${it}") }
if (!required) result.append(" (optional)")
result.append(" ${type.description}")
deprecatedWarning?.let { result.append(" Warning: $it") }
result.append("\n")
return result.toString()
}
}
@@ -3,7 +3,7 @@
* that can be found in the LICENSE file.
*/
package org.jetbrains.kliopt
package kotlinx.cli
import kotlin.annotation.AnnotationTarget.*
@@ -15,7 +15,7 @@ import kotlin.annotation.AnnotationTarget.*
*
* Any usage of a declaration annotated with `@ExperimentalCli` must be accepted either by
* annotating that usage with the [UseExperimental] annotation, e.g. `@UseExperimental(ExperimentalCli::class)`,
* or by using the compiler argument `-Xuse-experimental=org.jetbrains.kliopt.ExperimentalCli`.
* or by using the compiler argument `-Xuse-experimental=kotlinx.cli.ExperimentalCli`.
*/
@Experimental(level = Experimental.Level.WARNING)
@Retention(AnnotationRetention.BINARY)
@@ -0,0 +1,209 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlinx.cli
import kotlin.reflect.KProperty
/**
* Base interface for all possible types of options with multiple values.
*/
interface MultipleOptionType
/**
* Type of option with multiple values that can be provided several times in command line.
*/
class RepeatedOption: MultipleOptionType
/**
* Type of option with multiple values that are provided using delimiter.
*/
class DelimitedOption: MultipleOptionType
/**
* Type of option with multiple values that can be both provided several times in command line and using delimiter.
*/
class RepeatedDelimitedOption: MultipleOptionType
/**
* Option instance.
*/
abstract class Option<TResult> internal constructor(owner: CLIEntityWrapper): CLIEntity<TResult>(owner)
/**
* Common single option instance.
*/
abstract class AbstractSingleOption<T: Any, TResult> internal constructor(owner: CLIEntityWrapper): Option<TResult>(owner) {
/**
* Check descriptor for this kind of option.
*/
internal fun checkDescriptor(descriptor: OptionDescriptor<*, *>) {
if (descriptor.multiple || descriptor.delimiter != null) {
failAssertion("Option with single value can't be initialized with descriptor for multiple values.")
}
}
}
/**
* Option wit single non-nullable value.
*/
class SingleOption<T : Any> internal constructor(descriptor: OptionDescriptor<T, T>, owner: CLIEntityWrapper):
AbstractSingleOption<T, T>(owner) {
init {
checkDescriptor(descriptor)
delegate = ArgumentSingleValue(descriptor)
}
}
/**
* Option with single nullable value.
*/
class SingleNullableOption<T : Any> internal constructor(descriptor: OptionDescriptor<T, T>, owner: CLIEntityWrapper):
AbstractSingleOption<T, T?>(owner) {
init {
checkDescriptor(descriptor)
delegate = ArgumentSingleNullableValue(descriptor)
}
}
/**
* Option with multiple values.
*/
class MultipleOption<T : Any, OptionType: MultipleOptionType> internal constructor(descriptor: OptionDescriptor<T, List<T>>, owner: CLIEntityWrapper):
Option<List<T>>(owner) {
init {
if (!descriptor.multiple && descriptor.delimiter == null) {
failAssertion("Option with multiple values can't be initialized with descriptor for single one.")
}
delegate = ArgumentMultipleValues(descriptor)
}
}
/**
* Allow option have several values.
*/
fun <T : Any, TResult> AbstractSingleOption<T, TResult>.multiple(): MultipleOption<T, RepeatedOption> {
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
MultipleOption<T, RepeatedOption>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, listOfNotNull(defaultValue),
required, true, delimiter, deprecatedWarning), owner)
}
owner.entity = newOption
return newOption
}
/**
* Allow option have several values.
*/
fun <T : Any> MultipleOption<T, DelimitedOption>.multiple(): MultipleOption<T, RepeatedDelimitedOption> {
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
if (multiple) {
error("Try to use modifier multiple() twice on option ${fullName ?: ""}")
}
MultipleOption<T, RepeatedDelimitedOption>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, defaultValue?.toList() ?: listOf(),
required, true, delimiter, deprecatedWarning), owner)
}
owner.entity = newOption
return newOption
}
/**
* Set default option value.
*
* @param value default value.
*/
fun <T: Any, TResult> AbstractSingleOption<T, TResult>.default(value: T): SingleOption<T> {
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
if (required) {
printWarning("required() is unneeded, because option with default value is defined.")
}
SingleOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, value, required, multiple, delimiter, deprecatedWarning), owner)
}
owner.entity = newOption
return newOption
}
/**
* Set default option value.
*
* @param value default value.
*/
fun <T: Any, OptionType: MultipleOptionType>
MultipleOption<T, OptionType>.default(value: Collection<T>): MultipleOption<T, OptionType> {
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
if (required) {
printWarning("required() is unneeded, because option with default value is defined.")
}
MultipleOption<T, OptionType>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName,
shortName, description, value.toList(),
required, multiple, delimiter, deprecatedWarning), owner)
}
owner.entity = newOption
return newOption
}
/**
* Require option to be always provided in command line.
*/
fun <T: Any, TResult> AbstractSingleOption<T, TResult>.required(): SingleOption<T> {
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
defaultValue?.let {
printWarning("required() is unneeded, because option with default value is defined.")
}
SingleOption(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName,
shortName, description, defaultValue,
true, multiple, delimiter, deprecatedWarning), owner)
}
owner.entity = newOption
return newOption
}
/**
* Require option to be always provided in command line.
*/
fun <T: Any, OptionType: MultipleOptionType>
MultipleOption<T, OptionType>.required(): MultipleOption<T, OptionType> {
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
if (required) {
printWarning("required() is unneeded, because option with default value is defined.")
}
MultipleOption<T, OptionType>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, defaultValue?.toList() ?: listOf(),
true, multiple, delimiter, deprecatedWarning), owner)
}
owner.entity = newOption
return newOption
}
/**
* Allow provide several options using [delimiter].
*
* @param delimiterValue delimiter used to separate string value to option values.
*/
fun <T : Any, TResult> AbstractSingleOption<T, TResult>.delimiter(delimiterValue: String): MultipleOption<T, DelimitedOption> {
val newOption = with((delegate as ParsingValue<T, T>).descriptor as OptionDescriptor) {
MultipleOption<T, DelimitedOption>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, listOfNotNull(defaultValue),
required, multiple, delimiterValue, deprecatedWarning), owner)
}
owner.entity = newOption
return newOption
}
/**
* Allow provide several options using [delimiter].
*
* @param delimiterValue delimiter used to separate string value to option values.
*/
fun <T : Any> MultipleOption<T, RepeatedOption>.delimiter(delimiterValue: String): MultipleOption<T, RepeatedDelimitedOption> {
val newOption = with((delegate as ParsingValue<T, List<T>>).descriptor as OptionDescriptor) {
MultipleOption<T, RepeatedDelimitedOption>(OptionDescriptor(optionFullFormPrefix, optionShortFromPrefix, type, fullName, shortName,
description, defaultValue?.toList() ?: listOf(),
required, multiple, delimiterValue, deprecatedWarning), owner)
}
owner.entity = newOption
return newOption
}
@@ -3,8 +3,10 @@
* that can be found in the LICENSE file.
*/
package org.jetbrains.kliopt
package kotlinx.cli
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlin.test.*
class ArgumentsTests {
@@ -24,7 +26,7 @@ class ArgumentsTests {
fun testArgumetsWithAnyNumberOfValues() {
val argParser = ArgParser("testParser")
val output by argParser.argument(ArgType.String, "output", "Output file")
val inputs by argParser.arguments(ArgType.String, description = "Input files")
val inputs by argParser.argument(ArgType.String, description = "Input files").number()
argParser.parse(arrayOf("out.txt", "input1.txt", "input2.txt", "input3.txt",
"input4.txt"))
assertEquals("out.txt", output)
@@ -34,7 +36,7 @@ class ArgumentsTests {
@Test
fun testArgumetsWithSeveralValues() {
val argParser = ArgParser("testParser")
val addendums by argParser.arguments(ArgType.Int, "addendums", 2, description = "Addendums")
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").number(2)
val output by argParser.argument(ArgType.String, "output", "Output file")
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
argParser.parse(arrayOf("2", "-d", "3", "out.txt"))
@@ -48,7 +50,7 @@ class ArgumentsTests {
@Test
fun testSkippingExtraArguments() {
val argParser = ArgParser("testParser", skipExtraArguments = true)
val addendums by argParser.arguments(ArgType.Int, "addendums", 2, description = "Addendums")
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").number(2)
val output by argParser.argument(ArgType.String, "output", "Output file")
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
argParser.parse(arrayOf("2", "-d", "3", "out.txt", "something", "else", "in", "string"))
@@ -3,20 +3,22 @@
* that can be found in the LICENSE file.
*/
package org.jetbrains.kliopt
package kotlinx.cli
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlin.test.*
class ErrorTests {
@Test
fun testExtraArguments() {
val argParser = ArgParser("testParser")
val addendums by argParser.arguments(ArgType.Int, "addendums", 2, description = "Addendums")
val addendums by argParser.argument(ArgType.Int, "addendums", description = "Addendums").number(2)
val output by argParser.argument(ArgType.String, "output", "Output file")
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
val exception = assertFailsWith<IllegalStateException> { argParser.parse(
arrayOf("2", "-d", "3", "out.txt", "something", "else", "in", "string")) }
assertTrue("Too many arguments! Couldn't proccess argument something" in exception.message!!)
assertTrue("Too many arguments! Couldn't process argument something" in exception.message!!)
}
@Test
@@ -43,10 +45,9 @@ class ErrorTests {
@Test
fun testWrongChoice() {
val argParser = ArgParser("testParser")
val useShortForm by argParser.option(ArgType.Boolean, "short", "s", "Show short version of report",
defaultValue = false)
val renders by argParser.options(ArgType.Choice(listOf("text", "html")),
"renders", "r", "Renders for showing information", listOf("text"), multiple = true)
val useShortForm by argParser.option(ArgType.Boolean, "short", "s", "Show short version of report").default(false)
val renders by argParser.option(ArgType.Choice(listOf("text", "html")),
"renders", "r", "Renders for showing information").multiple().default(listOf("text"))
val exception = assertFailsWith<IllegalStateException> {
argParser.parse(arrayOf("-r", "xml"))
}
@@ -3,8 +3,12 @@
* that can be found in the LICENSE file.
*/
@file:UseExperimental(ExperimentalCli::class)
package org.jetbrains.kliopt
package kotlinx.cli
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.ExperimentalCli
import kotlinx.cli.Subcommand
import kotlin.math.exp
import kotlin.test.*
@@ -13,19 +17,19 @@ class HelpTests {
fun testHelpMessage() {
val argParser = ArgParser("test")
val mainReport by argParser.argument(ArgType.String, description = "Main report for analysis")
val compareToReport by argParser.argument(ArgType.String, description = "Report to compare to", required = false)
val compareToReport by argParser.argument(ArgType.String, description = "Report to compare to").optional()
val output by argParser.option(ArgType.String, shortName = "o", description = "Output file")
val epsValue by argParser.option(ArgType.Double, "eps", "e", "Meaningful performance changes", 1.0)
val epsValue by argParser.option(ArgType.Double, "eps", "e", "Meaningful performance changes").default(1.0)
val useShortForm by argParser.option(ArgType.Boolean, "short", "s",
"Show short version of report", defaultValue = false)
val renders by argParser.options(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics")),
shortName = "r", description = "Renders for showing information", defaultValue = listOf("text"), multiple = true)
"Show short version of report").default(false)
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics")),
shortName = "r", description = "Renders for showing information").multiple().default(listOf("text"))
val user by argParser.option(ArgType.String, shortName = "u", description = "User access information for authorization")
argParser.parse(arrayOf("-h"))
argParser.parse(arrayOf("main.txt"))
val helpOutput = argParser.makeUsage().trimIndent()
val expectedOutput = """
Usage: test options_list
Usage: test options_list
Arguments:
mainReport -> Main report for analysis { String }
compareToReport -> Report to compare to (optional) { String }
@@ -37,31 +41,28 @@ Options:
--user, -u -> User access information for authorization { String }
--help, -h -> Usage info
""".trimIndent()
assertEquals(helpOutput, expectedOutput)
assertEquals(expectedOutput, helpOutput)
}
@Test
fun testHelpForSubcommands() {
class Summary: Subcommand("summary") {
val exec by option(ArgType.Choice(listOf("samples", "geomean")),
description = "Execution time way of calculation", defaultValue = "geomean")
val execSamples by options(ArgType.String, "exec-samples",
description = "Samples used for execution time metric (value 'all' allows use all samples)",
delimiter = ",")
description = "Execution time way of calculation").default("geomean")
val execSamples by option(ArgType.String, "exec-samples",
description = "Samples used for execution time metric (value 'all' allows use all samples)").delimiter(",")
val execNormalize by option(ArgType.String, "exec-normalize",
description = "File with golden results which should be used for normalization")
val compile by option(ArgType.Choice(listOf("samples", "geomean")),
description = "Compile time way of calculation", defaultValue = "geomean")
val compileSamples by options(ArgType.String, "compile-samples",
description = "Samples used for compile time metric (value 'all' allows use all samples)",
delimiter = ",")
description = "Compile time way of calculation").default("geomean")
val compileSamples by option(ArgType.String, "compile-samples",
description = "Samples used for compile time metric (value 'all' allows use all samples)").delimiter(",")
val compileNormalize by option(ArgType.String, "compile-normalize",
description = "File with golden results which should be used for normalization")
val codesize by option(ArgType.Choice(listOf("samples", "geomean")),
description = "Code size way of calculation", defaultValue = "geomean")
val codesizeSamples by options(ArgType.String, "codesize-samples",
description = "Samples used for code size metric (value 'all' allows use all samples)",
delimiter = ",")
description = "Code size way of calculation").default("geomean")
val codesizeSamples by option(ArgType.String, "codesize-samples",
description = "Samples used for code size metric (value 'all' allows use all samples)").delimiter(",")
val codesizeNormalize by option(ArgType.String, "codesize-normalize",
description = "File with golden results which should be used for normalization")
val user by option(ArgType.String, shortName = "u", description = "User access information for authorization")
@@ -75,21 +76,25 @@ Options:
// Parse args.
val argParser = ArgParser("test")
argParser.subcommands(action)
argParser.parse(arrayOf("summary", "-h"))
val helpOutput = argParser.makeUsage().trimIndent()
argParser.parse(arrayOf("summary", "out.txt"))
val helpOutput = action.makeUsage().trimIndent()
val expectedOutput = """
Usage: test summary options_list
Usage: test summary options_list
Arguments:
mainReport -> Main report for analysis { String }
compareToReport -> Report to compare to (optional) { String }
Options:
--output, -o -> Output file { String }
--eps, -e [1.0] -> Meaningful performance changes { Double }
--short, -s [false] -> Show short version of report
--renders, -r [text] -> Renders for showing information { Value should be one of [text, html, teamcity, statistics, metrics] }
--exec [geomean] -> Execution time way of calculation { Value should be one of [samples, geomean] }
--exec-samples -> Samples used for execution time metric (value 'all' allows use all samples) { String }
--exec-normalize -> File with golden results which should be used for normalization { String }
--compile [geomean] -> Compile time way of calculation { Value should be one of [samples, geomean] }
--compile-samples -> Samples used for compile time metric (value 'all' allows use all samples) { String }
--compile-normalize -> File with golden results which should be used for normalization { String }
--codesize [geomean] -> Code size way of calculation { Value should be one of [samples, geomean] }
--codesize-samples -> Samples used for code size metric (value 'all' allows use all samples) { String }
--codesize-normalize -> File with golden results which should be used for normalization { String }
--user, -u -> User access information for authorization { String }
--help, -h -> Usage info
""".trimIndent()
assertEquals(helpOutput, expectedOutput)
""".trimIndent()
assertEquals(expectedOutput, helpOutput)
}
}
@@ -3,8 +3,10 @@
* that can be found in the LICENSE file.
*/
package org.jetbrains.kliopt
package kotlinx.cli
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlin.test.*
class OptionsTests {
@@ -41,9 +43,9 @@ class OptionsTests {
@Test
fun testMultipleOptions() {
val argParser = ArgParser("testParser")
val useShortForm by argParser.option(ArgType.Boolean, "short", "s", "Show short version of report", defaultValue = false)
val renders by argParser.options(ArgType.Choice(listOf("text", "html", "xml", "json")),
"renders", "r", "Renders for showing information", listOf("text"), multiple = true)
val useShortForm by argParser.option(ArgType.Boolean, "short", "s", "Show short version of report").default(false)
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "xml", "json")),
"renders", "r", "Renders for showing information").multiple().default(listOf("text"))
argParser.parse(arrayOf("-s", "-r", "text", "-r", "json"))
assertEquals(true, useShortForm)
assertEquals(2, renders.size)
@@ -55,12 +57,34 @@ class OptionsTests {
@Test
fun testDefaultOptions() {
val argParser = ArgParser("testParser")
val useShortForm by argParser.option(ArgType.Boolean, "short", "s", "Show short version of report", defaultValue = false)
val renders by argParser.options(ArgType.Choice(listOf("text", "html", "xml", "json")),
"renders", "r", "Renders for showing information", listOf("text"), multiple = true)
val useShortForm by argParser.option(ArgType.Boolean, "short", "s", "Show short version of report").default(false)
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "xml", "json")),
"renders", "r", "Renders for showing information").multiple().default(listOf("text"))
val output by argParser.option(ArgType.String, "output", "o", "Output file")
argParser.parse(arrayOf("-o", "out.txt"))
assertEquals(false, useShortForm)
assertEquals("text", renders[0])
}
@Test
fun testResetOptionsValues() {
val argParser = ArgParser("testParser")
val useShortFormOption = argParser.option(ArgType.Boolean, "short", "s", "Show short version of report").default(false)
var useShortForm by useShortFormOption
val rendersOption = argParser.option(ArgType.Choice(listOf("text", "html", "xml", "json")),
"renders", "r", "Renders for showing information").multiple().default(listOf("text"))
var renders by rendersOption
val outputOption = argParser.option(ArgType.String, "output", "o", "Output file")
var output by outputOption
argParser.parse(arrayOf("-o", "out.txt"))
output = null
useShortForm = true
renders = listOf()
assertEquals(true, useShortForm)
assertEquals(null, output)
assertEquals(0, renders.size)
assertEquals(ArgParser.ValueOrigin.REDEFINED, outputOption.valueOrigin)
assertEquals(ArgParser.ValueOrigin.REDEFINED, useShortFormOption.valueOrigin)
assertEquals(ArgParser.ValueOrigin.REDEFINED, rendersOption.valueOrigin)
}
}
@@ -3,8 +3,12 @@
* that can be found in the LICENSE file.
*/
@file:UseExperimental(ExperimentalCli::class)
package org.jetbrains.kliopt
package kotlinx.cli
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.ExperimentalCli
import kotlinx.cli.Subcommand
import kotlin.test.*
class SubcommandsTests {
@@ -14,7 +18,7 @@ class SubcommandsTests {
val output by argParser.option(ArgType.String, "output", "o", "Output file")
class Summary: Subcommand("summary") {
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
val addendums by arguments(ArgType.Int, "addendums", description = "Addendums")
val addendums by argument(ArgType.Int, "addendums", description = "Addendums").number()
var result: Int = 0
override fun execute() {
@@ -32,7 +36,7 @@ class SubcommandsTests {
@Test
fun testCommonOptions() {
abstract class CommonOptions(name: String): Subcommand(name) {
val numbers by arguments(ArgType.Int, "numbers", description = "Numbers")
val numbers by argument(ArgType.Int, "numbers", description = "Numbers").number()
}
class Summary: CommonOptions("summary") {
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
@@ -70,7 +74,7 @@ class SubcommandsTests {
val argParser = ArgParser("testParser")
class Summary: Subcommand("summary") {
val addendums by arguments(ArgType.Int, "addendums", description = "Addendums")
val addendums by argument(ArgType.Int, "addendums", description = "Addendums").number()
var result: Int = 0
override fun execute() {
@@ -15,7 +15,8 @@ interface KonanLibraryResolver {
fun resolveWithDependencies(
unresolvedLibraries: List<UnresolvedLibrary>,
noStdLib: Boolean = false,
noDefaultLibs: Boolean = false
noDefaultLibs: Boolean = false,
noEndorsedLibs: Boolean = false
): KonanLibraryResolveResult
}
@@ -30,13 +30,14 @@ internal class KonanLibraryResolverImpl(
override fun resolveWithDependencies(
unresolvedLibraries: List<UnresolvedLibrary>,
noStdLib: Boolean,
noDefaultLibs: Boolean
) = findLibraries(unresolvedLibraries, noStdLib, noDefaultLibs)
noDefaultLibs: Boolean,
noEndorsedLibs: Boolean
) = findLibraries(unresolvedLibraries, noStdLib, noDefaultLibs, noEndorsedLibs)
.leaveDistinct()
.resolveDependencies()
/**
* Returns the list of libraries based on [libraryNames], [noStdLib] and [noDefaultLibs] criteria.
* Returns the list of libraries based on [libraryNames], [noStdLib], [noDefaultLibs] and [noEndorsedLibs] criteria.
*
* This method does not return any libraries that might be available via transitive dependencies
* from the original library set (root set).
@@ -44,14 +45,15 @@ internal class KonanLibraryResolverImpl(
private fun findLibraries(
unresolvedLibraries: List<UnresolvedLibrary>,
noStdLib: Boolean,
noDefaultLibs: Boolean
noDefaultLibs: Boolean,
noEndorsedLibs: Boolean
): List<KonanLibrary> {
val userProvidedLibraries = unresolvedLibraries.asSequence()
.map { searchPathResolver.resolve(it) }
.toList()
val defaultLibraries = searchPathResolver.defaultLinks(noStdLib, noDefaultLibs)
val defaultLibraries = searchPathResolver.defaultLinks(noStdLib, noDefaultLibs, noEndorsedLibs)
// Make sure the user provided ones appear first, so that
// they have precedence over defaults when duplicates are eliminated.
@@ -160,7 +160,7 @@ class Library(val name: String, val requestedRepository: String?, val target: St
distributionKlib = Distribution().klib,
skipCurrentDir = true,
logger = KlibToolLogger)
resolver.defaultLinks(false, true)
resolver.defaultLinks(false, true, true)
.mapTo(defaultModules) {
DefaultDeserializedDescriptorFactory.createDescriptor(
it, versionSpec, storageManager, module.builtIns)
+1 -1
View File
@@ -17,7 +17,7 @@ benchmark {
mingwSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/mingw")
posixSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/posix")
dependencies.common(project(":endorsedLibraries:kliopt"))
dependencies.common(project(":endorsedLibraries:kotlinx.cli"))
}
val native = kotlin.targets.getByName("native") as KotlinNativeTarget
+1 -1
View File
@@ -19,7 +19,7 @@ import org.jetbrains.structsProducedByMacrosBenchmarks.*
import org.jetbrains.benchmarksLauncher.*
import org.jetbrains.structsBenchmarks.*
import org.jetbrains.typesBenchmarks.*
import org.jetbrains.kliopt.*
import kotlinx.cli.*
class CinteropLauncher : Launcher() {
override val benchmarks = BenchmarksCollection(
+2
View File
@@ -37,6 +37,8 @@ kotlin {
}
kotlin.srcDir "$toolsPath/benchmarks/shared/src"
kotlin.srcDir "$toolsPath/benchmarksAnalyzer/src/main/kotlin"
kotlin.srcDir "$rootProject.projectDir/endorsedLibraries/kotlinx.cli/src/main/kotlin"
kotlin.srcDir "$rootProject.projectDir/endorsedLibraries/kotlinx.cli/src/main/kotlin-native"
kotlin.srcDir "$toolsPath/benchmarksAnalyzer/src/main/kotlin-native"
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ benchmark {
posixSrcDirs = listOf("../shared/src/main/kotlin-native/posix")
linkerOpts = listOf("$buildDir/pi.o")
dependencies.common(project(":endorsedLibraries:kliopt"))
dependencies.common(project(":endorsedLibraries:kotlinx.cli"))
}
val compileLibary by tasks.creating {
@@ -4,7 +4,7 @@
*/
import org.jetbrains.benchmarksLauncher.*
import org.jetbrains.kliopt.*
import kotlinx.cli.*
expect class NumericalLauncher() : Launcher {
}
+1 -1
View File
@@ -21,7 +21,7 @@ benchmark {
posixSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/posix")
linkerOpts = listOf("-L$buildDir", "-lcomplexnumbers")
dependencies.common(project(":endorsedLibraries:kliopt"))
dependencies.common(project(":endorsedLibraries:kotlinx.cli"))
}
val compileLibary by tasks.creating {
@@ -6,7 +6,7 @@
import org.jetbrains.benchmarksLauncher.*
import org.jetbrains.complexNumbers.*
import org.jetbrains.kliopt.*
import kotlinx.cli.*
class ObjCInteropLauncher: Launcher() {
override val benchmarks = BenchmarksCollection(
+1 -1
View File
@@ -15,5 +15,5 @@ benchmark {
mingwSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/mingw")
posixSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native/posix")
dependencies.common(project(":endorsedLibraries:kliopt"))
dependencies.common(project(":endorsedLibraries:kotlinx.cli"))
}
+1 -1
View File
@@ -18,7 +18,7 @@
import org.jetbrains.ring.*
import octoTest
import org.jetbrains.benchmarksLauncher.*
import org.jetbrains.kliopt.*
import kotlinx.cli.*
class RingLauncher : Launcher() {
@@ -17,7 +17,7 @@
package org.jetbrains.benchmarksLauncher
import org.jetbrains.report.BenchmarkResult
import org.jetbrains.kliopt.*
import kotlinx.cli.*
abstract class Launcher {
@@ -118,17 +118,18 @@ abstract class Launcher {
abstract class BenchmarkArguments(argParser: ArgParser)
class BaseBenchmarkArguments(argParser: ArgParser): BenchmarkArguments(argParser) {
val warmup by argParser.option(ArgType.Int, shortName = "w", description = "Number of warm up iterations",
defaultValue = 20)
val repeat by argParser.option(ArgType.Int, shortName = "r", description = "Number of each benchmark run",
defaultValue = 60)
val prefix by argParser.option(ArgType.String, shortName = "p", description = "Prefix added to benchmark name",
defaultValue = "")
val warmup by argParser.option(ArgType.Int, shortName = "w", description = "Number of warm up iterations")
.default(20)
val repeat by argParser.option(ArgType.Int, shortName = "r", description = "Number of each benchmark run").
default(60)
val prefix by argParser.option(ArgType.String, shortName = "p", description = "Prefix added to benchmark name")
.default("")
val output by argParser.option(ArgType.String, shortName = "o", description = "Output file")
val filter by argParser.options(ArgType.String, shortName = "f", description = "Benchmark to run", multiple = true)
val filterRegex by argParser.options(ArgType.String, shortName = "fr",
description = "Benchmark to run, described by a regular expression", multiple = true)
val verbose by argParser.option(ArgType.Boolean, shortName = "v", description = "Verbose mode of running", defaultValue = false)
val filter by argParser.option(ArgType.String, shortName = "f", description = "Benchmark to run").multiple()
val filterRegex by argParser.option(ArgType.String, shortName = "fr",
description = "Benchmark to run, described by a regular expression").multiple()
val verbose by argParser.option(ArgType.Boolean, shortName = "v", description = "Verbose mode of running")
.default(false)
}
object BenchmarksRunner {
+3 -3
View File
@@ -36,7 +36,7 @@ kotlin {
commonMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
implementation project(":endorsedLibraries:kliopt")
implementation project(":endorsedLibraries:kotlinx.cli")
}
kotlin.srcDir "src"
kotlin.srcDir "../shared/src/main/kotlin"
@@ -47,9 +47,9 @@ kotlin {
kotlin.srcDir "../shared/src/main/kotlin-native/common"
kotlin.srcDir "../shared/src/main/kotlin-native/posix"
// Exclude kliopt added in commonMain dependencies and inherited by macosMain.
// Exclude kotlinx.cli added in commonMain dependencies and inherited by macosMain.
configurations.getByName(implementationConfigurationName) {
exclude module: "kliopt"
exclude module: "kotlinx.cli"
}
}
}
@@ -4,7 +4,7 @@
*/
import org.jetbrains.benchmarksLauncher.*
import org.jetbrains.kliopt.*
import kotlinx.cli.*
class SwiftLauncher: Launcher() {
override val benchmarks = BenchmarksCollection(
+1
View File
@@ -67,6 +67,7 @@ project.rootProject.ext.platformManager.enabled.each { target ->
defFile df.file
artifactName df.name
noDefaultLibs true
noEndorsedLibs true
libraries {
klibs df.config.depends
}
+1 -1
View File
@@ -45,7 +45,7 @@ if (System.getProperty("os.name") == "Mac OS X") {
include ':platformLibs'
include ':endorsedLibraries'
include ':endorsedLibraries:kliopt'
include ':endorsedLibraries:kotlinx.cli'
if (hasProperty("kotlinProjectPath")) {
include ':runtime:generator'
@@ -22,7 +22,7 @@ interface SearchPathResolver : WithLogger {
fun resolutionSequence(givenPath: String): Sequence<File>
fun resolve(unresolved: UnresolvedLibrary, isDefaultLink: Boolean = false): KonanLibraryImpl
fun resolve(givenPath: String): KonanLibraryImpl
fun defaultLinks(noStdLib: Boolean, noDefaultLibs: Boolean): List<KonanLibraryImpl>
fun defaultLinks(noStdLib: Boolean, noDefaultLibs: Boolean, noEndorsedLibs: Boolean): List<KonanLibraryImpl>
}
// FIXME(ddol): KLIB-REFACTORING-CLEANUP: remove this interface!
@@ -157,21 +157,34 @@ internal open class KonanLibrarySearchPathResolver(
val defaultRoots: List<File>
get() = listOfNotNull(distHead, distPlatformHead).filter { it.exists }
override fun defaultLinks(noStdLib: Boolean, noDefaultLibs: Boolean): List<KonanLibraryImpl> {
private fun getDefaultLibrariesFromDir(directory: File) =
if (directory.exists) {
directory.listFiles
.asSequence()
.filterNot { it.name.startsWith('.') }
.filterNot { it.name.removeSuffixIfPresent(KLIB_FILE_EXTENSION_WITH_DOT) == KONAN_STDLIB_NAME }
.map { UnresolvedLibrary(it.absolutePath, null) }
.map { resolve(it, isDefaultLink = true) }
} else emptySequence()
override fun defaultLinks(noStdLib: Boolean, noDefaultLibs: Boolean, noEndorsedLibs: Boolean): List<KonanLibraryImpl> {
val result = mutableListOf<KonanLibraryImpl>()
if (!noStdLib) {
result.add(resolve(UnresolvedLibrary(KONAN_STDLIB_NAME, null), true))
}
// Endorsed libraries in distHead.
if (!noEndorsedLibs) {
distHead?.let {
result.addAll(getDefaultLibrariesFromDir(it))
}
}
// Platform libraries resolve.
if (!noDefaultLibs) {
val defaultLibs = defaultRoots.flatMap { it.listFiles }
.asSequence()
.filterNot { it.name.startsWith('.') }
.filterNot { it.name.removeSuffixIfPresent(KLIB_FILE_EXTENSION_WITH_DOT) == KONAN_STDLIB_NAME }
.map { UnresolvedLibrary(it.absolutePath, null) }
.map { resolve(it, isDefaultLink = true) }
result.addAll(defaultLibs)
distPlatformHead?.let {
result.addAll(getDefaultLibrariesFromDir(it))
}
}
return result
+4 -4
View File
@@ -48,7 +48,7 @@ kotlin {
}
kotlin.srcDir '../benchmarks/shared/src'
kotlin.srcDir 'src/main/kotlin'
kotlin.srcDir '../../endorsedLibraries/kliopt/src/main/kotlin'
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin'
}
commonTest {
dependencies {
@@ -71,21 +71,21 @@ kotlin {
nativeMain {
dependsOn commonMain
kotlin.srcDir 'src/main/kotlin-native'
kotlin.srcDir '../../endorsedLibraries/kliopt/src/main/kotlin-native'
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin-native'
}
jvmMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
}
kotlin.srcDir 'src/main/kotlin-jvm'
kotlin.srcDir '../../endorsedLibraries/kliopt/src/main/kotlin-jvm'
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin-jvm'
}
jsMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
}
kotlin.srcDir 'src/main/kotlin-js'
kotlin.srcDir '../../endorsedLibraries/kliopt/src/main/kotlin-js'
kotlin.srcDir '../../endorsedLibraries/kotlinx.cli/src/main/kotlin-js'
}
linuxMain { dependsOn nativeMain }
windowsMain { dependsOn nativeMain }
@@ -17,7 +17,7 @@
import org.jetbrains.analyzer.sendGetRequest
import org.jetbrains.analyzer.readFile
import org.jetbrains.analyzer.SummaryBenchmarksReport
import org.jetbrains.kliopt.*
import kotlinx.cli.*
import org.jetbrains.renders.*
import org.jetbrains.report.BenchmarksReport
import org.jetbrains.report.BenchmarkResult
@@ -106,31 +106,30 @@ fun parseNormalizeResults(results: String): Map<String, Map<String, Double>> {
fun main(args: Array<String>) {
class Summary: Subcommand("summary") {
val exec by option(ArgType.Choice(listOf("samples", "geomean")),
description = "Execution time way of calculation", defaultValue = "geomean")
val execSamples by options(ArgType.String, "exec-samples",
description = "Samples used for execution time metric (value 'all' allows use all samples)",
delimiter = ",")
description = "Execution time way of calculation").default("geomean")
val execSamples by option(ArgType.String, "exec-samples",
description = "Samples used for execution time metric (value 'all' allows use all samples)")
.delimiter(",")
val execNormalize by option(ArgType.String, "exec-normalize",
description = "File with golden results which should be used for normalization")
val compile by option(ArgType.Choice(listOf("samples", "geomean")),
description = "Compile time way of calculation", defaultValue = "geomean")
val compileSamples by options(ArgType.String, "compile-samples",
description = "Samples used for compile time metric (value 'all' allows use all samples)",
delimiter = ",")
description = "Compile time way of calculation").default("geomean")
val compileSamples by option(ArgType.String, "compile-samples",
description = "Samples used for compile time metric (value 'all' allows use all samples)")
.delimiter(",")
val compileNormalize by option(ArgType.String, "compile-normalize",
description = "File with golden results which should be used for normalization")
val codesize by option(ArgType.Choice(listOf("samples", "geomean")),
description = "Code size way of calculation", defaultValue = "geomean")
val codesizeSamples by options(ArgType.String, "codesize-samples",
description = "Samples used for code size metric (value 'all' allows use all samples)",
delimiter = ",")
description = "Code size way of calculation").default("geomean")
val codesizeSamples by option(ArgType.String, "codesize-samples",
description = "Samples used for code size metric (value 'all' allows use all samples)").delimiter(",")
val codesizeNormalize by option(ArgType.String, "codesize-normalize",
description = "File with golden results which should be used for normalization")
val user by option(ArgType.String, shortName = "u", description = "User access information for authorization")
val mainReport by argument(ArgType.String, description = "Main report for analysis")
override fun execute() {
val benchsReport = SummaryBenchmarksReport(getBenchmarkReport(mainReport!!, user))
val benchsReport = SummaryBenchmarksReport(getBenchmarkReport(mainReport, user))
val results = mutableListOf<String>()
val executionNormalize = execNormalize?.let {
parseNormalizeResults(getFileContent(it))
@@ -171,19 +170,20 @@ fun main(args: Array<String>) {
val argParser = ArgParser("benchmarksAnalyzer")
argParser.subcommands(action)
val mainReport by argParser.argument(ArgType.String, description = "Main report for analysis")
val compareToReport by argParser.argument(ArgType.String, description = "Report to compare to", required = false)
val compareToReport by argParser.argument(ArgType.String, description = "Report to compare to").optional()
val output by argParser.option(ArgType.String, shortName = "o", description = "Output file")
val epsValue by argParser.option(ArgType.Double, "eps", "e", "Meaningful performance changes", 1.0)
val epsValue by argParser.option(ArgType.Double, "eps", "e",
"Meaningful performance changes").default(1.0)
val useShortForm by argParser.option(ArgType.Boolean, "short", "s",
"Show short version of report", defaultValue = false)
val renders by argParser.options(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics")),
shortName = "r", description = "Renders for showing information", defaultValue = listOf("text"), multiple = true)
"Show short version of report").default(false)
val renders by argParser.option(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics")),
shortName = "r", description = "Renders for showing information").multiple().default(listOf("text"))
val user by argParser.option(ArgType.String, shortName = "u", description = "User access information for authorization")
if (argParser.parse(args).commandName == "benchmarksAnalyzer") {
// Read contents of file.
val mainBenchsReport = getBenchmarkReport(mainReport!!, user)
val mainBenchsReport = getBenchmarkReport(mainReport, user)
var compareToBenchsReport = compareToReport?.let {
getBenchmarkReport(it, user)
}
@@ -194,7 +194,7 @@ fun main(args: Array<String>) {
epsValue)
var outputFile = output
renders?.forEach {
renders.forEach {
Render.getRenderByName(it).print(summaryReport, useShortForm, outputFile)
outputFile = null
}
@@ -160,6 +160,7 @@ abstract class KonanBuildingConfig<T: KonanBuildingTask>(private val name_: Stri
override fun libraries(configure: KonanLibrariesSpec.() -> Unit) = forEach { it.libraries(configure) }
override fun noDefaultLibs(flag: Boolean) = forEach { it.noDefaultLibs(flag) }
override fun noEndorsedLibs(flag: Boolean) = forEach { it.noEndorsedLibs(flag) }
override fun dumpParameters(flag: Boolean) = forEach { it.dumpParameters(flag) }
@@ -229,6 +229,7 @@ internal fun dumpProperties(task: Task) {
println("enableOptimization : $enableOptimizations")
println("enableAssertions : $enableAssertions")
println("noDefaultLibs : $noDefaultLibs")
println("noEndorsedLibs : $noEndorsedLibs")
println("target : $target")
println("languageVersion : $languageVersion")
println("apiVersion : $apiVersion")
@@ -30,6 +30,7 @@ interface KonanArtifactWithLibrariesSpec: KonanArtifactSpec {
fun libraries(configure: KonanLibrariesSpec.() -> Unit)
fun noDefaultLibs(flag: Boolean)
fun noEndorsedLibs(flag: Boolean)
fun dependencies(closure: Closure<Unit>)
}
@@ -167,6 +167,9 @@ abstract class KonanArtifactWithLibrariesTask: KonanArtifactTask(), KonanArtifac
@Input
var noDefaultLibs = false
@Input
var noEndorsedLibs = false
// DSL
override fun libraries(closure: Closure<Unit>) = libraries(ConfigureUtil.configureUsing(closure))
@@ -176,4 +179,8 @@ abstract class KonanArtifactWithLibrariesTask: KonanArtifactTask(), KonanArtifac
override fun noDefaultLibs(flag: Boolean) {
noDefaultLibs = flag
}
override fun noEndorsedLibs(flag: Boolean) {
noEndorsedLibs = flag
}
}
@@ -213,7 +213,8 @@ abstract class KonanCompileTask: KonanBuildingTask(), KonanCompileSpec {
addKey("-ea", enableAssertions)
addKey("-Xtime", measureTime)
addKey("-Xprofile-phases", measureTime)
addKey("-nodefaultlibs", noDefaultLibs)
addKey("-no-default-libs", noDefaultLibs)
addKey("-no-endorsed-libs", noEndorsedLibs)
addKey("-Xmulti-platform", enableMultiplatform)
if (libraries.friends.isNotEmpty())
@@ -105,7 +105,8 @@ open class KonanInteropTask @Inject constructor(val workerExecutor: WorkerExecut
addArgs("-library", libraries.namedKlibs)
addArgs("-library", libraries.artifacts.map { it.artifact.canonicalPath })
addKey("-nodefaultlibs", noDefaultLibs)
addKey("-no-default-libs", noDefaultLibs)
addKey("-no-endorsed-libs", noEndorsedLibs)
addAll(extraOpts)
}
@@ -110,6 +110,7 @@ class IncrementalSpecification extends BaseKonanSpecification {
"artifactName" | "'foo'"
"extraOpts" | "'-Xtime'"
"noDefaultLibs" | "true"
"noEndorsedLibs" | "true"
}
def 'Plugin should support a custom entry point and recompile an artifact if it changes'() {
@@ -210,6 +211,7 @@ class IncrementalSpecification extends BaseKonanSpecification {
"includeDirs.allHeaders" | "'src'"
"extraOpts" | "'-verbose'"
"noDefaultLibs" | "true"
"noEndorsedLibs" | "true"
}
def 'includeDirs.headerFilterOnly change should cause recompilation and interop reprocessing'() {
@@ -57,11 +57,13 @@ class LibrarySpecification extends BaseKonanSpecification {
|}
""".stripMargin(), ArtifactType.LIBRARY)
project.addSetting(name, "noDefaultLibs", "true")
project.addSetting(name, "noEndorsedLibs", "true")
}
void createInteropLibrary(KonanProject project, String name) {
project.addCompilerArtifact(name, "headers = math.h", ArtifactType.INTEROP)
project.addSetting(name, "noDefaultLibs", "true")
project.addSetting(name, "noEndorsedLibs", "true")
}
KonanProject createProjectWithLibraries(Closure createLibraryFunction, Closure callBuilder) {
@@ -79,6 +81,7 @@ class LibrarySpecification extends BaseKonanSpecification {
result.addCompilerArtifact("main", createMainWithCalls(libraryNames, callBuilder))
result.addSetting("main", "noDefaultLibs", "true")
result.addSetting("main", "noEndorsedLibs", "true")
for (int i = 0; i < libraries.size(); i++) {
def foo = libraryNames[i].first
@@ -130,12 +133,15 @@ class LibrarySpecification extends BaseKonanSpecification {
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
it.addSetting("foo", "noDefaultLibs", "true")
it.addSetting("foo", "noEndorsedLibs", "true")
it.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
it.addSetting("bar", "noDefaultLibs", "true")
it.addSetting("bar", "noEndorsedLibs", "true")
it.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
it.addSetting("main", "noDefaultLibs", "true" )
it.addSetting("main", "noEndorsedLibs", "true" )
it.addLibraryToArtifactCustom("main", "allLibrariesFrom project")
}
project.createRunner()
@@ -153,15 +159,19 @@ class LibrarySpecification extends BaseKonanSpecification {
project.addCompilerArtifact("wrongFoo","fun foo() { println(24) }", ArtifactType.LIBRARY)
project.addSetting("wrongFoo", "noDefaultLibs", "true")
project.addSetting("wrongFoo", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
subproject.addSetting("foo", "noDefaultLibs", "true")
subproject.addSetting("foo", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
subproject.addSetting("bar", "noDefaultLibs", "true")
subproject.addSetting("bar", "noEndorsedLibs", "true")
project.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
project.addSetting("main", "noDefaultLibs", "true" )
project.addSetting("main", "noEndorsedLibs", "true" )
project.addLibraryToArtifactCustom("main", "allLibrariesFrom project('subproject')")
project.createRunner()
@@ -180,15 +190,19 @@ class LibrarySpecification extends BaseKonanSpecification {
project.addCompilerArtifact("wrongFoo1", "fun foo() { println(42) }", ArtifactType.LIBRARY)
project.addSetting("wrongFoo1", "noDefaultLibs", "true")
project.addSetting("wrongFoo1", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("wrongFoo2", "fun foo() { println(42) }", ArtifactType.LIBRARY)
subproject.addSetting("wrongFoo2", "noDefaultLibs", "true")
subproject.addSetting("wrongFoo2", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("math1", "headers = math.h", ArtifactType.INTEROP)
subproject.addSetting("math1", "noDefaultLibs", "true")
subproject.addSetting("math1", "noEndorsedLibs", "true")
subproject.addCompilerArtifact("math2", "headers = math.h", ArtifactType.INTEROP)
subproject.addSetting("math2", "noDefaultLibs", "true")
subproject.addSetting("math2", "noEndorsedLibs", "true")
project.addCompilerArtifact("main" ,"""
|fun foo() {}
@@ -196,6 +210,7 @@ class LibrarySpecification extends BaseKonanSpecification {
|fun main(args: Array<String>) { foo(); math1.cos(0.0); math2.cos(0.0) }
""".stripMargin())
project.addSetting("main", "noDefaultLibs", "true" )
project.addSetting("main", "noEndorsedLibs", "true" )
project.addLibraryToArtifactCustom("main", "allInteropLibrariesFrom project('subproject')")
@@ -209,12 +224,15 @@ class LibrarySpecification extends BaseKonanSpecification {
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("wrongFoo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
it.addSetting("wrongFoo", "noDefaultLibs", "true")
it.addSetting("wrongFoo", "noEndorsedLibs", "true")
it.addCompilerArtifact("math1", "headers = math.h", ArtifactType.INTEROP)
it.addSetting("math1", "noDefaultLibs", "true")
it.addSetting("math1", "noEndorsedLibs", "true")
it.addCompilerArtifact("math2", "headers = math.h", ArtifactType.INTEROP)
it.addSetting("math2", "noDefaultLibs", "true")
it.addSetting("math2", "noEndorsedLibs", "true")
it.addCompilerArtifact("main" ,"""
|fun foo() {}
@@ -222,6 +240,7 @@ class LibrarySpecification extends BaseKonanSpecification {
|fun main(args: Array<String>) { foo(); math1.cos(0.0); math2.cos(0.0) }
""".stripMargin())
it.addSetting("main", "noDefaultLibs", "true" )
it.addSetting("main", "noEndorsedLibs", "true" )
it.addLibraryToArtifactCustom("main", "allInteropLibrariesFrom project")
}
project.createRunner()
@@ -234,11 +253,13 @@ class LibrarySpecification extends BaseKonanSpecification {
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
it.addSetting("foo", "noDefaultLibs", "true")
it.addSetting("foo", "noEndorsedLibs", "true")
it.addSetting("foo", "baseDir", "file('out')")
it.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo() }")
it.addSetting("main", "noDefaultLibs", "true" )
it.addSetting("main", "noEndorsedLibs", "true" )
it.addSetting("main", "dependsOn", "konanArtifacts.foo.$KonanProject.HOST")
it.addLibraryToArtifactCustom("main", "klib 'foo'")
it.addLibraryToArtifactCustom("main", "useRepo 'out/$KonanProject.HOST'")
@@ -253,13 +274,16 @@ class LibrarySpecification extends BaseKonanSpecification {
def project = KonanProject.createEmpty(projectDirectory) { KonanProject it ->
it.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
it.addSetting("foo", "noDefaultLibs", "true")
it.addSetting("foo", "noEndorsedLibs", "true")
it.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
it.addSetting("bar", "noDefaultLibs", "true")
it.addSetting("bar", "noEndorsedLibs", "true")
it.addLibraryToArtifact("bar", "foo")
it.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
it.addSetting("main", "noDefaultLibs", "true" )
it.addSetting("main", "noEndorsedLibs", "true" )
it.addLibraryToArtifact("main", "bar")
}
@@ -281,15 +305,18 @@ class LibrarySpecification extends BaseKonanSpecification {
subproject1.addCompilerArtifact("foo", "fun foo() { println(42) }", ArtifactType.LIBRARY)
subproject1.addSetting("foo", "noDefaultLibs", "true")
subproject1.addSetting("foo", "noEndorsedLibs", "true")
subproject2.addCompilerArtifact("bar", "fun bar() { println(43) }", ArtifactType.LIBRARY)
subproject2.addSetting("bar", "noDefaultLibs", "true")
subproject2.addSetting("bar", "noEndorsedLibs", "true")
subproject2.addLibraryToArtifactCustom(
"bar", "artifact rootProject.project('subproject1'), 'foo'"
)
project.addCompilerArtifact("main" ,"fun main(args: Array<String>) { foo(); bar() }")
project.addSetting("main", "noDefaultLibs", "true" )
project.addSetting("main", "noEndorsedLibs", "true" )
project.addLibraryToArtifactCustom(
"main", "artifact project('subproject2'), 'bar'"
)
@@ -629,7 +629,8 @@ class ExperimentalPluginTests {
dependencies {
implementation project(':libBar')
cinterop('mystdio') {
extraOpts '-nodefaultlibs'
extraOpts '-no-default-libs'
extraOpts '-no-endorsed-libs'
}
}
}
@@ -19,7 +19,8 @@ fun invokeInterop(flavor: String, args: Array<String>): Array<String> {
val arguments = if (flavor == "native") CInteropArguments() else JSInteropArguments()
arguments.argParser.parse(args)
val outputFileName = arguments.output
val noDefaultLibs = arguments.nodefaultlibs
val noDefaultLibs = arguments.nodefaultlibs || arguments.nodefaultlibsDeprecated
val noEndorsedLibs = arguments.noendorsedlibs
val purgeUserLibs = arguments.purgeUserLibs
val temporaryFilesDir = arguments.tempDir
@@ -49,7 +50,8 @@ fun invokeInterop(flavor: String, args: Array<String>): Array<String> {
Distribution()
).libraryResolver()
val allLibraries = resolver.resolveWithDependencies(
libraries.toUnresolvedLibraries, noStdLib = true, noDefaultLibs = noDefaultLibs
libraries.toUnresolvedLibraries, noStdLib = true, noDefaultLibs = noDefaultLibs,
noEndorsedLibs = noEndorsedLibs
).getFullList()
val imports = allLibraries.map { library ->
@@ -82,6 +84,7 @@ fun invokeInterop(flavor: String, args: Array<String>): Array<String> {
libraries.flatMap { listOf("-library", it) } +
repos.flatMap { listOf("-repo", it) } +
(if (noDefaultLibs) arrayOf("-$NODEFAULTLIBS") else emptyArray()) +
(if (noEndorsedLibs) arrayOf("-$NOENDORSEDLIBS") else emptyArray()) +
(if (purgeUserLibs) arrayOf("-$PURGE_USER_LIBS") else emptyArray())
return konancArgs