New CLI tool (#3085)

This commit is contained in:
LepilkinaElena
2019-07-22 15:55:38 +03:00
committed by GitHub
parent e881ebdf38
commit 3c2a5b0392
42 changed files with 1852 additions and 614 deletions
+3 -3
View File
@@ -135,9 +135,9 @@ To update the blackbox compiler tests set TeamCity build number in `gradle.prope
cd tools/benchmarksAnalyzer/build/bin/<target>/benchmarksAnalyzerReleaseExecutable/
./benchmarksAnalyzer.kexe <file1> <file2>
Tool has several renders which allow produce output report in different forms (text, html, etc.). To set up render use flag `-render/-r`.
Output can be redirected to file with flag `-output/-o`.
To get detailed information about supported options, please use `-help/-h`.
Tool has several renders which allow produce output report in different forms (text, html, etc.). To set up render use flag `--render/-r`.
Output can be redirected to file with flag `--output/-o`.
To get detailed information about supported options, please use `--help/-h`.
Analyzer tool can compare both local files and files placed on Bintray/TeamCity.
+1 -4
View File
@@ -29,18 +29,15 @@ repositories {
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$buildKotlinVersion"
compile "org.jetbrains.kotlin:kotlin-compiler:$buildKotlinVersion"
compile project(':Interop:Indexer')
compile "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
compile project(':endorsedLibraries:kliopt')
}
compileKotlin {
sourceSets {
main.kotlin.srcDirs += "$rootDir/tools/kliopt"
}
kotlinOptions {
freeCompilerArgs = ['-Xuse-experimental=kotlin.ExperimentalUnsignedTypes']
}
@@ -6,7 +6,7 @@ import org.jetbrains.kotlin.native.interop.gen.jvm.buildNativeLibrary
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.getCInteropArguments
import org.jetbrains.kotlin.native.interop.tool.CInteropArguments
import org.jetbrains.kliopt.ArgParser
import java.io.File
@@ -39,13 +39,13 @@ private fun makeDependencyAssigner(targets: List<String>, defFiles: List<File>)
private fun makeDependencyAssignerForTarget(target: String, defFiles: List<File>): SingleTargetDependencyAssigner {
val tool = prepareTool(target, KotlinPlatform.NATIVE)
val argParser = ArgParser(getCInteropArguments(), useDefaultHelpShortName = false)
argParser.parse(arrayOf<String>())
val cinteropArguments = CInteropArguments()
cinteropArguments.argParser.parse(arrayOf<String>())
val libraries = defFiles.associateWith {
buildNativeLibrary(
tool,
DefFile(it, tool.substitutions),
argParser,
cinteropArguments,
ImportsImpl(emptyMap())
).getHeaderPaths()
}
@@ -25,75 +25,78 @@ const val TEMP_DIR = "Xtemporary-files-dir"
// TODO: unify camel and snake cases.
// Possible solution is to accept both cases
fun getCommonInteropArguments() = listOf(
OptionDescriptor(ArgType.Boolean(), "verbose", description = "Enable verbose logging output", defaultValue = "false"),
OptionDescriptor(ArgType.Choice(listOf("jvm", "native", "wasm")),
"flavor", description = "Interop target", defaultValue = "jvm"),
OptionDescriptor(ArgType.String(), "pkg", description = "place generated bindings to the package"),
OptionDescriptor(ArgType.String(), "output", "o", "specifies the resulting library file", defaultValue = "nativelib"),
OptionDescriptor(ArgType.String(), "libraryPath", description = "add a library search path",
isMultiple = true, delimiter = ","),
OptionDescriptor(ArgType.String(), "staticLibrary", description = "embed static library to the result",
isMultiple = true, delimiter = ","),
OptionDescriptor(ArgType.String(), "generated", description = "place generated bindings to the directory",
defaultValue = System.getProperty("user.dir")),
OptionDescriptor(ArgType.String(), "natives", description = "where to put the built native files",
defaultValue = System.getProperty("user.dir")),
OptionDescriptor(ArgType.String(), "library", "l", "library to use for building", isMultiple = true),
OptionDescriptor(ArgType.String(), "repo", "r",
"repository to resolve dependencies", isMultiple = true),
OptionDescriptor(ArgType.Boolean(), NODEFAULTLIBS, description = "don't link the libraries from dist/klib automatically",
defaultValue = "false"),
OptionDescriptor(ArgType.Boolean(), PURGE_USER_LIBS, description = "don't link unused libraries even explicitly specified",
defaultValue = "false"),
OptionDescriptor(ArgType.String(), TEMP_DIR, description = "save temporary files to the given directory")
)
fun getCInteropArguments(): List<OptionDescriptor> {
val options = listOf(
OptionDescriptor(ArgType.String(), "target", description = "native target to compile to", defaultValue = "host"),
OptionDescriptor(ArgType.String(), "def", description = "the library definition file"),
OptionDescriptor(ArgType.String(), "header", description = "header file to produce kotlin bindings for",
isMultiple = true, delimiter = ","),
OptionDescriptor(ArgType.String(), "h", description = "header file to produce kotlin bindings for",
isMultiple = true, delimiter = ",", deprecatedWarning = "Option -h is deprecated. Please use -header."),
OptionDescriptor(ArgType.String(), HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX, "hfasp",
"header file to produce kotlin bindings for", isMultiple = true, delimiter = ","),
OptionDescriptor(ArgType.String(), "compilerOpts",
description = "additional compiler options (allows to add several options separated by spaces)",
isMultiple = true, delimiter = " "),
OptionDescriptor(ArgType.String(), "compiler-options",
description = "additional compiler options (allows to add several options separated by spaces)",
isMultiple = true, delimiter = " "),
OptionDescriptor(ArgType.String(), "linkerOpts",
description = "additional linker options (allows to add several options separated by spaces)",
isMultiple = true, delimiter = " "),
OptionDescriptor(ArgType.String(), "linker-options",
description = "additional linker options (allows to add several options separated by spaces)",
isMultiple = true, delimiter = " "),
OptionDescriptor(ArgType.String(), "compiler-option",
description = "additional compiler option", isMultiple = true),
OptionDescriptor(ArgType.String(), "linker-option",
description = "additional linker option", isMultiple = true),
OptionDescriptor(ArgType.String(), "copt", description = "additional compiler options (allows to add several options separated by spaces)",
isMultiple = true, delimiter = " ", deprecatedWarning = "Option -copt is deprecated. Please use -compiler-options."),
OptionDescriptor(ArgType.String(), "lopt", description = "additional linker options (allows to add several options separated by spaces)",
isMultiple = true, delimiter = " ", deprecatedWarning = "Option -lopt is deprecated. Please use -linker-options."),
OptionDescriptor(ArgType.String(), "linker", description = "use specified linker")
)
return (options + getCommonInteropArguments())
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 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 nodefaultlibs by argParser.option(ArgType.Boolean, NODEFAULTLIBS,
description = "don't link the libraries from dist/klib automatically",
defaultValue = false)
val purgeUserLibs by argParser.option(ArgType.Boolean, PURGE_USER_LIBS,
description = "don't link unused libraries even explicitly specified", defaultValue = false)
val tempDir by argParser.option(ArgType.String, TEMP_DIR,
description = "save temporary files to the given directory")
}
fun getJSInteropArguments(): List<OptionDescriptor> {
val options = listOf(
OptionDescriptor(ArgType.Choice(listOf("wasm32")), "target", description = "wasm target to compile to", defaultValue = "wasm32")
)
return (options + getCommonInteropArguments())
class CInteropArguments(argParser: ArgParser =
ArgParser("cinterop", useDefaultHelpShortName = false,
prefixStyle = ArgParser.OPTION_PREFIX_STYLE.JVM)): CommonInteropArguments(argParser) {
val target by argParser.option(ArgType.String, description = "native target to compile to", defaultValue = "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 linker by argParser.option(ArgType.String, description = "use specified linker")
}
class JSInteropArguments(argParser: ArgParser = ArgParser("jsinterop", useDefaultHelpShortName = false,
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")
}
internal fun warn(msg: String) {
println("warning: $msg")
}
fun ArgParser.getValuesAsArray(propertyName: String) =
(getAll<String>(propertyName) ?: listOf<String>()).toTypedArray()
@@ -158,35 +158,34 @@ private fun findFilesByGlobs(roots: List<Path>, globs: List<String>): Map<Path,
}
private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> = mapOf()): Array<String>? {
val argParser = ArgParser(getCInteropArguments(), useDefaultHelpShortName = false)
if (!argParser.parse(args))
return null
val ktGenRoot = argParser.get<String>("generated")
val nativeLibsDir = argParser.get<String>("natives")
val flavorName = argParser.get<String>("flavor")
private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> = mapOf()): Array<String> {
val cinteropArguments = CInteropArguments()
cinteropArguments.argParser.parse(args)
val ktGenRoot = cinteropArguments.generated
val nativeLibsDir = cinteropArguments.natives
val flavorName = cinteropArguments.flavor
val flavor = KotlinPlatform.values().single { it.name.equals(flavorName, ignoreCase = true) }
val defFile = argParser.get<String>("def")?.let { File(it) }
val defFile = cinteropArguments.def?.let { File(it) }
val manifestAddend = (additionalArgs["manifest"] as? String)?.let { File(it) }
if (defFile == null && argParser.get<String>("pkg") == null) {
argParser.printError("-def or -pkg should provided!")
if (defFile == null && cinteropArguments.pkg == null) {
cinteropArguments.argParser.printError("-def or -pkg should provided!")
}
val tool = prepareTool(argParser.get<String>("target"), flavor)
val tool = prepareTool(cinteropArguments.target, flavor)
val def = DefFile(defFile, tool.substitutions)
val isLinkerOptsSetByUser = (argParser.getOrigin("linkerOpts") == ArgParser.ValueOrigin.SET_BY_USER) ||
(argParser.getOrigin("linker-option") == ArgParser.ValueOrigin.SET_BY_USER) ||
(argParser.getOrigin("linker-options") == ArgParser.ValueOrigin.SET_BY_USER) ||
(argParser.getOrigin("lopt") == ArgParser.ValueOrigin.SET_BY_USER)
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)
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.")
}
val additionalLinkerOpts = argParser.getValuesAsArray("linkerOpts") + argParser.getValuesAsArray("linker-option") +
argParser.getValuesAsArray("linker-options") + argParser.getValuesAsArray("lopt")
val verbose = argParser.get<Boolean>("verbose")!!
val additionalLinkerOpts = cinteropArguments.linkerOpts.toTypedArray() + cinteropArguments.linkerOption.toTypedArray() +
cinteropArguments.linkerOptions.toTypedArray() + cinteropArguments.lopt.toTypedArray()
val verbose = cinteropArguments.verbose
val language = selectNativeLanguage(def.config)
@@ -195,14 +194,14 @@ private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> =
def.config.linkerOpts.toTypedArray() +
tool.defaultCompilerOpts +
additionalLinkerOpts
val linkerName = argParser.get<String>("linker") ?: def.config.linker
val linkerName = cinteropArguments.linker ?: def.config.linker
val linker = "${tool.llvmHome}/bin/$linkerName"
val compiler = "${tool.llvmHome}/bin/clang"
val excludedFunctions = def.config.excludedFunctions.toSet()
val excludedMacros = def.config.excludedMacros.toSet()
val staticLibraries = def.config.staticLibraries + argParser.getValuesAsArray("staticLibrary")
val libraryPaths = def.config.libraryPaths + argParser.getValuesAsArray("libraryPath")
val fqParts = (argParser.get<String>("pkg") ?: def.config.packageName)?.split('.')
val staticLibraries = def.config.staticLibraries + cinteropArguments.staticLibrary.toTypedArray()
val libraryPaths = def.config.libraryPaths + cinteropArguments.libraryPath.toTypedArray()
val fqParts = (cinteropArguments.pkg ?: def.config.packageName)?.split('.')
?: defFile!!.name.split('.').reversed().drop(1)
val outKtFileName = fqParts.last() + ".kt"
@@ -213,11 +212,11 @@ private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> =
val libName = (additionalArgs["cstubsname"] as? String)?: fqParts.joinToString("") + "stubs"
val tempFiles = TempFiles(libName, argParser.get<String>("Xtemporary-files-dir"))
val tempFiles = TempFiles(libName, cinteropArguments.tempDir)
val imports = parseImports((additionalArgs["import"] as? List<String>)?.toTypedArray() ?: arrayOf())
val library = buildNativeLibrary(tool, def, argParser, imports)
val library = buildNativeLibrary(tool, def, cinteropArguments, imports)
val (nativeIndex, compilation) = buildNativeIndex(library, verbose)
@@ -303,13 +302,13 @@ internal fun prepareTool(target: String?, flavor: KotlinPlatform): ToolConfig {
internal fun buildNativeLibrary(
tool: ToolConfig,
def: DefFile,
arguments: ArgParser,
arguments: CInteropArguments,
imports: ImportsImpl
): NativeLibrary {
val additionalHeaders = arguments.getValuesAsArray("header") + arguments.getValuesAsArray("h")
val additionalCompilerOpts = arguments.getValuesAsArray("compilerOpts") +
arguments.getValuesAsArray("compiler-options") + arguments.getValuesAsArray("compiler-option") +
arguments.getValuesAsArray("copt")
val additionalHeaders = (arguments.header + arguments.shortHeaderForm).toTypedArray()
val additionalCompilerOpts = (arguments.compilerOpts +
arguments.compilerOptions + arguments.compilerOption +
arguments.copt).toTypedArray()
val headerFiles = def.config.headers + additionalHeaders
val language = selectNativeLanguage(def.config)
@@ -317,7 +316,7 @@ internal fun buildNativeLibrary(
addAll(def.config.compilerOpts)
addAll(tool.defaultCompilerOpts)
addAll(additionalCompilerOpts)
addAll(getCompilerFlagsForVfsOverlay(arguments.getValuesAsArray("headerFilterAdditionalSearchPrefix"), def))
addAll(getCompilerFlagsForVfsOverlay(arguments.headerFilterPrefix.toTypedArray(), def))
addAll(when (language) {
Language.C -> emptyList()
Language.OBJECTIVE_C -> {
@@ -4,8 +4,7 @@ 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 org.jetbrains.kotlin.native.interop.tool.getJSInteropArguments
import org.jetbrains.kotlin.native.interop.tool.getValuesAsArray
import org.jetbrains.kotlin.native.interop.tool.JSInteropArguments
fun kotlinHeader(packageName: String): String {
return "package $packageName\n" +
@@ -392,22 +391,20 @@ fun generateJs(interfaces: List<Interface>): String =
const val idlMathPackage = "kotlinx.interop.wasm.math"
const val idlDomPackage = "kotlinx.interop.wasm.dom"
fun processIdlLib(args: Array<String>, additionalArgs: Map<String, Any> = mapOf()): Array<String>? {
val argParser = ArgParser(getJSInteropArguments(), useDefaultHelpShortName = false)
if (!argParser.parse(args))
return null
fun processIdlLib(args: Array<String>, additionalArgs: Map<String, Any> = mapOf()): Array<String> {
val jsInteropArguments = JSInteropArguments()
jsInteropArguments.argParser.parse(args)
// TODO: Refactor me.
val ktGenRoot = File(argParser.get<String>("generated")!!).mkdirs()
val nativeLibsDir = File(argParser.get<String>("natives")!!).mkdirs()
val ktGenRoot = File(jsInteropArguments.generated).mkdirs()
val nativeLibsDir = File(jsInteropArguments.natives).mkdirs()
val idl = when (argParser.get<String>("pkg")) {
idlMathPackage-> idlMath
val idl = when (jsInteropArguments.pkg) {
idlMathPackage -> idlMath
idlDomPackage -> idlDom
else -> throw IllegalArgumentException("Please choose either $idlMathPackage or $idlDomPackage for -pkg argument")
}
File(ktGenRoot, "kotlin_stubs.kt").writeText(generateKotlin(argParser.get<String>("pkg")!!, idl))
File(ktGenRoot, "kotlin_stubs.kt").writeText(generateKotlin(jsInteropArguments.pkg!!, idl))
File(nativeLibsDir, "js_stubs.js").writeText(generateJs(idl))
File((additionalArgs["manifest"] as? String)!!).writeText("") // The manifest is currently unused for wasm.
return argsToCompiler(argParser.getValuesAsArray("staticLibrary"), argParser.getValuesAsArray("libraryPath"))
return argsToCompiler(jsInteropArguments.staticLibrary.toTypedArray(), jsInteropArguments.libraryPath.toTypedArray())
}
@@ -301,6 +301,7 @@ class NativeInteropPlugin implements Plugin<Project> {
prj.dependencies {
interopStubGenerator project(path: ":Interop:StubGenerator")
interopStubGenerator project(path: ":endorsedLibraries:kliopt", configuration: "jvmRuntimeElements")
}
// FIXME: choose tasks more wisely
@@ -102,10 +102,10 @@ open class BuildRegister : DefaultTask() {
val target = System.getProperty("os.name").replace("\\s".toRegex(), "")
// Get summary information.
val output = arrayOf("$analyzer", "summary", "-exec-samples", "all", "-compile", "samples",
"-compile-samples", "HelloWorld,Videoplayer", "-codesize-samples", "all",
"-exec-normalize", "bintray:goldenResults.csv",
"-codesize-normalize", "bintray:goldenResults.csv", "$currentBenchmarksReportFile")
val output = arrayOf("$analyzer", "summary", "--exec-samples", "all", "--compile", "samples",
"--compile-samples", "HelloWorld,Videoplayer", "--codesize-samples", "all",
"--exec-normalize", "bintray:goldenResults.csv",
"--codesize-normalize", "bintray:goldenResults.csv", "$currentBenchmarksReportFile")
.runCommand()
// Postprocess information.
@@ -129,9 +129,9 @@ open class BuildRegister : DefaultTask() {
// Collect framework run details.
if (target == "MacOSX") {
val frameworkOutput = arrayOf("$analyzer", "summary", "-compile", "samples",
"-compile-samples", "FrameworkBenchmarksAnalyzer", "-codesize-samples", "FrameworkBenchmarksAnalyzer",
"-codesize-normalize", "bintray:goldenResults.csv", "$currentBenchmarksReportFile")
val frameworkOutput = arrayOf("$analyzer", "summary", "--compile", "samples",
"--compile-samples", "FrameworkBenchmarksAnalyzer", "--codesize-samples", "FrameworkBenchmarksAnalyzer",
"--codesize-normalize", "bintray:goldenResults.csv", "$currentBenchmarksReportFile")
.runCommand()
val buildInfoPartsFramework = frameworkOutput.split(',')
+28 -3
View File
@@ -226,6 +226,7 @@ task distCompiler(type: Copy) {
dependsOn ':utilities:jar'
dependsOn ':klib:jar'
dependsOn ':sharedJar'
dependsOn ':endorsedLibraries:jvmJar'
destinationDir distDir
@@ -285,6 +286,10 @@ task distCompiler(type: Copy) {
into('konan/lib')
}
from(project(':endorsedLibraries:kliopt').file('build/libs')) {
into('konan/lib')
}
from(file("${gradle.includedBuild('kotlin-native-shared').projectDir}/build/libs")) {
into('konan/lib')
}
@@ -323,7 +328,13 @@ task distRuntime(type: Copy) {
dependsOn('commonDistRuntime')
}
task distEndorsedLibraries {
dependsOn "${hostName}CrossDistEndorsedLibraries"
}
def stdlib = 'klib/common/stdlib'
def endorsedLibs = 'klib/common/endorsedLibraries'
def endorsedLibsBase = 'klib/common'
task commonDistRuntime(type: Copy) {
destinationDir distDir
@@ -340,6 +351,10 @@ task crossDistRuntime(type: Copy) {
dependsOn('commonDistRuntime')
}
task crossDistEndorsedLibraries(type: Copy) {
dependsOn.addAll(targetList.collect { "${it}CrossDistEndorsedLibraries" })
}
task crossDistPlatformLibs {
dependsOn.addAll(targetList.collect { "${it}PlatformLibs" })
}
@@ -378,7 +393,17 @@ targetList.each { target ->
}
task("${target}CrossDist") {
dependsOn "${target}CrossDistRuntime", 'distCompiler', 'commonDistRuntime'
dependsOn "${target}CrossDistRuntime", 'distCompiler', 'commonDistRuntime', "${target}CrossDistEndorsedLibraries"
}
task("${target}CrossDistEndorsedLibraries", type: Copy) {
dependsOn ":endorsedLibraries:${target}EndorsedLibraries"
destinationDir distDir
from(project(':endorsedLibraries').file("build")) {
include('**')
into("$endorsedLibsBase")
}
}
}
@@ -387,11 +412,11 @@ task distPlatformLibs {
}
task dist {
dependsOn 'distCompiler', 'distRuntime'
dependsOn 'distCompiler', 'distRuntime', 'distEndorsedLibraries'
}
task crossDist {
dependsOn 'crossDistRuntime', 'distCompiler'
dependsOn 'crossDistRuntime', 'distCompiler', 'crossDistEndorsedLibraries'
}
task bundle(type: (isWindows()) ? Zip : Tar) {
+2 -1
View File
@@ -89,12 +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"
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"
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"
TOOL_CLASS=org.jetbrains.kotlin.cli.utilities.MainKt
LIBCLANG_DISABLE_CRASH_RECOVERY=1 \
+2 -1
View File
@@ -55,6 +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 "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"
@@ -72,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%"
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 JAVA_OPTS=-ea ^
-Xmx3G ^
+29
View File
@@ -0,0 +1,29 @@
def endorsedLibrariesList = ['kliopt']
task clean {
doLast {
delete buildDir
}
}
task jvmJar {
endorsedLibrariesList.each { library ->
dependsOn "$library:jvmJar"
}
}
// Build all default libraries.
targetList.each { target ->
task("${target}EndorsedLibraries", type: Copy) {
endorsedLibrariesList.each { library ->
dependsOn "$library:${target}${library.capitalize()}"
}
destinationDir project.buildDir
endorsedLibrariesList.each { library ->
from(project("$library").file("build/${target}${library.capitalize()}")) {
include('**')
into("$library")
}
}
}
}
+105
View File
@@ -0,0 +1,105 @@
import org.jetbrains.kotlin.konan.target.HostManager
buildscript {
repositories {
maven {
url 'https://cache-redirector.jetbrains.com/jcenter'
}
maven {
url kotlinCompilerRepo
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
apply plugin: 'kotlin-multiplatform'
repositories {
maven {
url 'https://cache-redirector.jetbrains.com/jcenter'
}
maven {
url kotlinCompilerRepo
}
maven {
url buildKotlinCompilerRepo
}
}
kotlin {
sourceSets {
commonMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
}
kotlin.srcDir 'src/main/kotlin'
}
commonTest {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-test-common:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlinVersion"
}
kotlin.srcDir 'src/tests'
}
jvm().compilations.main.defaultSourceSet {
dependencies {
implementation kotlin('stdlib-jdk8')
}
kotlin.srcDir 'src/main/kotlin-jvm'
}
// JVM-specific tests and their dependencies:
jvm().compilations.test.defaultSourceSet {
dependencies {
implementation kotlin('test-junit')
}
}
jvm().compilations.all {
kotlinOptions {
freeCompilerArgs = ["-Xuse-experimental=org.jetbrains.kliopt.ExperimentalCli"]
suppressWarnings = true
}
}
}
}
def commonSrc = new File("$projectDir/src/main/kotlin")
def nativeSrc = new File("$projectDir/src/main/kotlin-native")
targetList.each { target ->
def konanJvmArgs = [*HostManager.regularJvmArgs,
"-Dkonan.home=${rootProject.projectDir}/dist",
"-Djava.library.path=${project.buildDir}/nativelibs/$hostName",
]
def defaultArgs = ['-nopack', '-nodefaultlibs']
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) {
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',
'-Xuse-experimental=kotlin.ExperimentalMultiplatform',
'-Xallow-result-return-type',
commonSrc.absolutePath,
"-Xcommon-sources=${commonSrc.absolutePath}",
nativeSrc]
inputs.dir(nativeSrc)
inputs.dir(commonSrc)
outputs.dir(project.file("build/${target}Kliopt"))
}
}
@@ -0,0 +1,2 @@
org.jetbrains.kotlin.native.home=../../dist
org.jetbrains.kotlin.native.jvmArgs=-Xmx6G
@@ -0,0 +1,10 @@
/*
* 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
internal actual fun exitProcess(status: Int): Nothing {
error("Not implemented for JS!")
}
@@ -0,0 +1,11 @@
/*
* 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
internal actual fun exitProcess(status: Int): Nothing {
kotlin.system.exitProcess(0)
}
@@ -0,0 +1,10 @@
/*
* 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
internal actual fun exitProcess(status: Int): Nothing {
kotlin.system.exitProcess(0)
}
@@ -0,0 +1,87 @@
/*
* 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
/**
* Possible types of arguments.
*
* Inheritors describe type of argument value. New types can be added by user.
* In case of options type can have parameter or not.
*/
abstract class ArgType<T : Any>(val hasParameter: kotlin.Boolean) {
/**
* Text description of type for helpMessage.
*/
abstract val description: kotlin.String
/**
* Function to convert string argument value to its type.
* In case of error during conversion also provides help message.
*
* @param value value
*/
abstract val conversion: (value: kotlin.String, name: kotlin.String, helpMessage: kotlin.String)->T
/**
* Argument type for flags that can be only set/unset.
*/
object Boolean : ArgType<kotlin.Boolean>(false) {
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 }
}
/**
* Argument type for string values.
*/
object String : ArgType<kotlin.String>(true) {
override val description: kotlin.String
get() = "{ String }"
override val conversion: (value: kotlin.String, name: kotlin.String, _: kotlin.String) -> kotlin.String
get() = { value, _, _ -> value }
}
/**
* Argument type for integer values.
*/
object Int : ArgType<kotlin.Int>(true) {
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") }
}
/**
* Argument type for double values.
*/
object Double : ArgType<kotlin.Double>(true) {
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") }
}
/**
* Type for arguments that have limited set of possible values.
*/
class Choice(val values: List<kotlin.String>) : ArgType<kotlin.String>(true) {
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") }
}
}
@@ -0,0 +1,36 @@
/*
* 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.annotation.AnnotationTarget.*
/**
* This annotation marks the experimental API for working with command line arguments.
*
* > Beware using the annotated API especially if you're developing a library, since your library might become binary incompatible
* with the future versions of the CLI library.
*
* 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`.
*/
@Experimental(level = Experimental.Level.WARNING)
@Retention(AnnotationRetention.BINARY)
@Target(
CLASS,
ANNOTATION_CLASS,
PROPERTY,
FIELD,
LOCAL_VARIABLE,
VALUE_PARAMETER,
CONSTRUCTOR,
FUNCTION,
PROPERTY_GETTER,
PROPERTY_SETTER,
TYPEALIAS
)
@SinceKotlin("1.3")
public annotation class ExperimentalCli
@@ -0,0 +1,863 @@
/*
* 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()
}
}
@@ -0,0 +1,57 @@
/*
* 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.test.*
class ArgumentsTests {
@Test
fun testPositionalArguments() {
val argParser = ArgParser("testParser")
val debugMode by argParser.option(ArgType.Boolean, "debug", "d", "Debug mode")
val input by argParser.argument(ArgType.String, "input", "Input file")
val output by argParser.argument(ArgType.String, "output", "Output file")
argParser.parse(arrayOf("-d", "input.txt", "out.txt"))
assertEquals(true, debugMode)
assertEquals("out.txt", output)
assertEquals("input.txt", input)
}
@Test
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")
argParser.parse(arrayOf("out.txt", "input1.txt", "input2.txt", "input3.txt",
"input4.txt"))
assertEquals("out.txt", output)
assertEquals(4, inputs.size)
}
@Test
fun testArgumetsWithSeveralValues() {
val argParser = ArgParser("testParser")
val addendums by argParser.arguments(ArgType.Int, "addendums", 2, description = "Addendums")
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"))
assertEquals("out.txt", output)
val (first, second) = addendums
assertEquals(2, addendums.size)
assertEquals(2, first)
assertEquals(3, second)
}
@Test
fun testSkippingExtraArguments() {
val argParser = ArgParser("testParser", skipExtraArguments = true)
val addendums by argParser.arguments(ArgType.Int, "addendums", 2, description = "Addendums")
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"))
assertEquals("out.txt", output)
}
}
@@ -0,0 +1,55 @@
/*
* 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.test.*
class ErrorTests {
@Test
fun testExtraArguments() {
val argParser = ArgParser("testParser")
val addendums by argParser.arguments(ArgType.Int, "addendums", 2, description = "Addendums")
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!!)
}
@Test
fun testUnknownOption() {
val argParser = ArgParser("testParser")
val output by argParser.option(ArgType.String, "output", "o", "Output file")
val input by argParser.option(ArgType.String, "input", "i", "Input file")
val exception = assertFailsWith<IllegalStateException> {
argParser.parse(arrayOf("-o", "out.txt", "-d", "-i", "input.txt"))
}
assertTrue("Unknown option -d" in exception.message!!)
}
@Test
fun testWrongFormat() {
val argParser = ArgParser("testParser")
val number by argParser.option(ArgType.Int, "number", description = "Integer number")
val exception = assertFailsWith<IllegalStateException> {
argParser.parse(arrayOf("--number", "out.txt"))
}
assertTrue("Option number is expected to be integer number. out.txt is provided." in exception.message!!)
}
@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 exception = assertFailsWith<IllegalStateException> {
argParser.parse(arrayOf("-r", "xml"))
}
assertTrue("Option renders is expected to be one of [text, html]. xml is provided." in exception.message!!)
}
}
@@ -0,0 +1,95 @@
/*
* 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.
*/
@file:UseExperimental(ExperimentalCli::class)
package org.jetbrains.kliopt
import kotlin.math.exp
import kotlin.test.*
class HelpTests {
@Test
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 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 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)
val user by argParser.option(ArgType.String, shortName = "u", description = "User access information for authorization")
argParser.parse(arrayOf("-h"))
val helpOutput = argParser.makeUsage().trimIndent()
val expectedOutput = """
Usage: test 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] }
--user, -u -> User access information for authorization { String }
--help, -h -> Usage info
""".trimIndent()
assertEquals(helpOutput, expectedOutput)
}
@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 = ",")
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 = ",")
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 = ",")
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() {
println("Do some important things!")
}
}
val action = Summary()
// Parse args.
val argParser = ArgParser("test")
argParser.subcommands(action)
argParser.parse(arrayOf("summary", "-h"))
val helpOutput = argParser.makeUsage().trimIndent()
val expectedOutput = """
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] }
--user, -u -> User access information for authorization { String }
--help, -h -> Usage info
""".trimIndent()
assertEquals(helpOutput, expectedOutput)
}
}
@@ -0,0 +1,66 @@
/*
* 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.test.*
class OptionsTests {
@Test
fun testShortForm() {
val argParser = ArgParser("testParser")
val output by argParser.option(ArgType.String, "output", "o", "Output file")
val input by argParser.option(ArgType.String, "input", "i", "Input file")
argParser.parse(arrayOf("-o", "out.txt", "-i", "input.txt"))
assertEquals("out.txt", output)
assertEquals("input.txt", input)
}
@Test
fun testFullForm() {
val argParser = ArgParser("testParser")
val output by argParser.option(ArgType.String, shortName = "o", description = "Output file")
val input by argParser.option(ArgType.String, shortName = "i", description = "Input file")
argParser.parse(arrayOf("--output", "out.txt", "--input", "input.txt"))
assertEquals("out.txt", output)
assertEquals("input.txt", input)
}
@Test
fun testJavaPrefix() {
val argParser = ArgParser("testParser", prefixStyle = ArgParser.OPTION_PREFIX_STYLE.JVM)
val output by argParser.option(ArgType.String, "output", "o", "Output file")
val input by argParser.option(ArgType.String, "input", "i", "Input file")
argParser.parse(arrayOf("-output", "out.txt", "-i", "input.txt"))
assertEquals("out.txt", output)
assertEquals("input.txt", input)
}
@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)
argParser.parse(arrayOf("-s", "-r", "text", "-r", "json"))
assertEquals(true, useShortForm)
assertEquals(2, renders.size)
val (firstRender, secondRender) = renders
assertEquals("text", firstRender)
assertEquals("json", secondRender)
}
@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 output by argParser.option(ArgType.String, "output", "o", "Output file")
argParser.parse(arrayOf("-o", "out.txt"))
assertEquals(false, useShortForm)
assertEquals("text", renders[0])
}
}
@@ -0,0 +1,99 @@
/*
* 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.
*/
@file:UseExperimental(ExperimentalCli::class)
package org.jetbrains.kliopt
import kotlin.test.*
class SubcommandsTests {
@Test
fun testSubcommand() {
val argParser = ArgParser("testParser")
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")
var result: Int = 0
override fun execute() {
result = addendums.sum()
result = if (invert!!) -1 * result else result
}
}
val action = Summary()
argParser.subcommands(action)
argParser.parse(arrayOf("-o", "out.txt", "summary", "-i", "2", "3", "5"))
assertEquals("out.txt", output)
assertEquals(-10, action.result)
}
@Test
fun testCommonOptions() {
abstract class CommonOptions(name: String): Subcommand(name) {
val numbers by arguments(ArgType.Int, "numbers", description = "Numbers")
}
class Summary: CommonOptions("summary") {
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
var result: Int = 0
override fun execute() {
result = numbers.sum()
result = invert?.let { -1 * result } ?: result
}
}
class Subtraction : CommonOptions("sub") {
var result: Int = 0
override fun execute() {
result = numbers.map { -it }.sum()
}
}
val summaryAction = Summary()
val subtractionAction = Subtraction()
val argParser = ArgParser("testParser")
argParser.subcommands(summaryAction, subtractionAction)
argParser.parse(arrayOf("summary", "2", "3", "5"))
assertEquals(10, summaryAction.result)
val argParserSubtraction = ArgParser("testParser")
argParserSubtraction.subcommands(summaryAction, subtractionAction)
argParserSubtraction.parse(arrayOf("sub", "8", "-2", "3"))
assertEquals(-9, subtractionAction.result)
}
@Test
fun testRecursiveSubcommands() {
val argParser = ArgParser("testParser")
class Summary: Subcommand("summary") {
val addendums by arguments(ArgType.Int, "addendums", description = "Addendums")
var result: Int = 0
override fun execute() {
result = addendums.sum()
}
}
class Calculation: Subcommand("calc") {
init {
subcommands(Summary())
}
val invert by option(ArgType.Boolean, "invert", "i", "Invert results")
var result: Int = 0
override fun execute() {
result = (subcommands["summary"] as Summary).result
result = if (invert!!) -1 * result else result
}
}
val action = Calculation()
argParser.subcommands(action)
argParser.parse(arrayOf("calc", "-i", "summary", "2", "3", "5"))
assertEquals(-10, action.result)
}
}
+1 -1
View File
@@ -170,5 +170,5 @@ task swiftinterop {
task videoplayer {
dependsOn 'clean'
dependsOn 'swiftinterop:konanRun'
dependsOn 'videoplayer:konanRun'
}
+1 -1
View File
@@ -11,7 +11,7 @@ plugins {
benchmark {
applicationName = "Cinterop"
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin", "../../tools/kliopt")
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin")
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native")
}
+5 -3
View File
@@ -39,8 +39,10 @@ class CinteropLauncher : Launcher() {
fun main(args: Array<String>) {
val launcher = CinteropLauncher()
BenchmarksRunner.runBenchmarks(args, { parser: ArgParser ->
launcher.launch(parser.get<Int>("warmup")!!, parser.get<Int>("repeat")!!, parser.get<String>("prefix")!!,
parser.getAll<String>("filter"), parser.getAll<String>("filterRegex"))
BenchmarksRunner.runBenchmarks(args, { arguments: BenchmarkArguments ->
if (arguments is BaseBenchmarkArguments) {
launcher.launch(arguments.warmup, arguments.repeat, arguments.prefix,
arguments.filter, arguments.filterRegex)
} else emptyList()
}, benchmarksListAction = launcher::benchmarksListAction)
}
+2 -1
View File
@@ -37,7 +37,8 @@ kotlin {
}
kotlin.srcDir "$toolsPath/benchmarks/shared/src"
kotlin.srcDir "$toolsPath/benchmarksAnalyzer/src/main/kotlin"
kotlin.srcDir "$toolsPath/kliopt"
kotlin.srcDir "$rootProject.projectDir/endorsedLibraries/kliopt/src/main/kotlin"
kotlin.srcDir "$rootProject.projectDir/endorsedLibraries/kliopt/src/main/kotlin-native"
kotlin.srcDir "$toolsPath/benchmarksAnalyzer/src/main/kotlin-native"
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ plugins {
benchmark {
applicationName = "ObjCInterop"
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin", "../../tools/kliopt")
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin")
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native")
linkerOpts = listOf("-L$buildDir", "-lcomplexnumbers")
@@ -26,8 +26,10 @@ class ObjCInteropLauncher: Launcher() {
fun main(args: Array<String>) {
val launcher = ObjCInteropLauncher()
BenchmarksRunner.runBenchmarks(args, { parser: ArgParser ->
launcher.launch(parser.get<Int>("warmup")!!, parser.get<Int>("repeat")!!, parser.get<String>("prefix")!!,
parser.getAll<String>("filter"), parser.getAll<String>("filterRegex"))
BenchmarksRunner.runBenchmarks(args, { arguments: BenchmarkArguments ->
if (arguments is BaseBenchmarkArguments) {
launcher.launch(arguments.warmup, arguments.repeat, arguments.prefix,
arguments.filter, arguments.filterRegex)
} else emptyList()
}, benchmarksListAction = launcher::benchmarksListAction)
}
+3 -3
View File
@@ -9,7 +9,7 @@ plugins {
benchmark {
applicationName = "Ring"
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin", "../../tools/kliopt")
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm")
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native")
commonSrcDirs = listOf("../../tools/benchmarks/shared/src", "src/main/kotlin", "../shared/src/main/kotlin", "../../endorsedLibraries/kliopt/src/main/kotlin")
jvmSrcDirs = listOf("src/main/kotlin-jvm", "../shared/src/main/kotlin-jvm", "../../endorsedLibraries/kliopt/src/main/kotlin-jvm")
nativeSrcDirs = listOf("src/main/kotlin-native", "../shared/src/main/kotlin-native", "../../endorsedLibraries/kliopt/src/main/kotlin-native")
}
+5 -3
View File
@@ -209,8 +209,10 @@ class RingLauncher : Launcher() {
fun main(args: Array<String>) {
val launcher = RingLauncher()
BenchmarksRunner.runBenchmarks(args, { parser: ArgParser ->
launcher.launch(parser.get<Int>("warmup")!!, parser.get<Int>("repeat")!!, parser.get<String>("prefix")!!,
parser.getAll<String>("filter"), parser.getAll<String>("filterRegex"))
BenchmarksRunner.runBenchmarks(args, { arguments: BenchmarkArguments ->
if (arguments is BaseBenchmarkArguments) {
launcher.launch(arguments.warmup, arguments.repeat, arguments.prefix,
arguments.filter, arguments.filterRegex)
} else emptyList()
}, benchmarksListAction = launcher::benchmarksListAction)
}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:UseExperimental(ExperimentalCli::class)
package org.jetbrains.benchmarksLauncher
import org.jetbrains.report.BenchmarkResult
@@ -87,46 +87,58 @@ abstract class Launcher {
return benchmarkResults
}
fun benchmarksListAction(argParser: ArgParser) {
fun benchmarksListAction() {
benchmarks.keys.forEach {
println(it)
}
}
}
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 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)
}
object BenchmarksRunner {
fun parse(args: Array<String>, benchmarksListAction: (ArgParser)->Unit): ArgParser? {
val actions = mapOf("list" to Action(
benchmarksListAction,
ArgParser(listOf<OptionDescriptor>()))
)
val options = listOf(
OptionDescriptor(ArgType.Int(), "warmup", "w", "Number of warm up iterations", "20"),
OptionDescriptor(ArgType.Int(), "repeat", "r", "Number of each benchmark run", "60"),
OptionDescriptor(ArgType.String(), "prefix", "p", "Prefix added to benchmark name", ""),
OptionDescriptor(ArgType.String(), "output", "o", "Output file"),
OptionDescriptor(ArgType.String(), "filter", "f", "Benchmark to run", isMultiple = true),
OptionDescriptor(ArgType.String(), "filterRegex", "fr", "Benchmark to run, described by a regular expression", isMultiple = true)
)
fun parse(args: Array<String>, benchmarksListAction: ()->Unit): BenchmarkArguments? {
class List: Subcommand("list") {
override fun execute() {
benchmarksListAction()
}
}
// Parse args.
val argParser = ArgParser(options, actions = actions)
return if (argParser.parse(args)) argParser else null
val argParser = ArgParser("benchmark")
argParser.subcommands(List())
val argumentsValues = BaseBenchmarkArguments(argParser)
return if (argParser.parse(args).commandName == "benchmark") argumentsValues else null
}
fun collect(results: List<BenchmarkResult>, parser: ArgParser) {
JsonReportCreator(results).printJsonReport(parser.get<String>("output"))
fun collect(results: List<BenchmarkResult>, arguments: BenchmarkArguments) {
if (arguments is BaseBenchmarkArguments) {
JsonReportCreator(results).printJsonReport(arguments.output)
}
}
fun runBenchmarks(args: Array<String>,
run: (parser: ArgParser) -> List<BenchmarkResult>,
parseArgs: (args: Array<String>, benchmarksListAction: (ArgParser)->Unit) -> ArgParser? = this::parse,
collect: (results: List<BenchmarkResult>, parser: ArgParser) -> Unit = this::collect,
benchmarksListAction: (ArgParser)->Unit) {
val parser = parseArgs(args, benchmarksListAction)
parser?.let {
val results = run(parser)
collect(results, parser)
run: (parser: BenchmarkArguments) -> List<BenchmarkResult>,
parseArgs: (args: Array<String>, benchmarksListAction: ()->Unit) -> BenchmarkArguments? = this::parse,
collect: (results: List<BenchmarkResult>, arguments: BenchmarkArguments) -> Unit = this::collect,
benchmarksListAction: ()->Unit) {
val arguments = parseArgs(args, benchmarksListAction)
arguments?.let {
val results = run(arguments)
collect(results, arguments)
}
}
}
+3 -2
View File
@@ -39,12 +39,13 @@ kotlin {
}
kotlin.srcDir "src"
kotlin.srcDir "../shared/src/main/kotlin"
kotlin.srcDir "$toolsPath/kliopt"
kotlin.srcDir "$toolsPath/benchmarks/shared/src"
kotlin.srcDir "$rootProject.projectDir/endorsedLibraries/kliopt/src/main/kotlin"
kotlin.srcDir "$toolsPath/benchmarks/shared/src/main/kotlin"
}
macosMain {
dependsOn commonMain
kotlin.srcDir "../shared/src/main/kotlin-native"
kotlin.srcDir "$rootProject.projectDir/endorsedLibraries/kliopt/src/main/kotlin-native"
}
}
+12 -8
View File
@@ -15,7 +15,7 @@ let companion = BenchmarkEntryWithInit.Companion()
var swiftLauncher = SwiftLauncher()
runner.runBenchmarks(args: args, run: { (parser: ArgParser) -> [BenchmarkResult] in
runner.runBenchmarks(args: args, run: { (arguments: BenchmarkArguments) -> [BenchmarkResult] in
swiftLauncher.add(name: "createMultigraphOfInt", benchmark: companion.create(ctor: { return SwiftInteropBenchmarks() },
lambda: { ($0 as! SwiftInteropBenchmarks).createMultigraphOfInt() }))
swiftLauncher.add(name: "fillCityMap", benchmark: companion.create(ctor: { return SwiftInteropBenchmarks() },
@@ -38,12 +38,16 @@ runner.runBenchmarks(args: args, run: { (parser: ArgParser) -> [BenchmarkResult]
lambda: { ($0 as! SwiftInteropBenchmarks).stringInterop() }))
swiftLauncher.add(name: "simpleFunction", benchmark: companion.create(ctor: { return SwiftInteropBenchmarks() },
lambda: { ($0 as! SwiftInteropBenchmarks).simpleFunction() }))
return swiftLauncher.launch(numWarmIterations: parser.get(name: "warmup") as! Int32,
numberOfAttempts: parser.get(name: "repeat") as! Int32,
prefix: parser.get(name: "prefix") as! String, filters: parser.getAll(name: "filter"),
filterRegexes: parser.getAll(name: "filterRegex"))
}, parseArgs: { (args: KotlinArray, benchmarksListAction: ((ArgParser) -> KotlinUnit)) -> ArgParser? in
if arguments is BaseBenchmarkArguments {
let argumentsList: BaseBenchmarkArguments = arguments as! BaseBenchmarkArguments
return swiftLauncher.launch(numWarmIterations: argumentsList.warmup,
numberOfAttempts: argumentsList.repeat,
prefix: argumentsList.prefix, filters: argumentsList.filter,
filterRegexes: argumentsList.filterRegex)
}
return [BenchmarkResult]()
}, parseArgs: { (args: KotlinArray, benchmarksListAction: (() -> KotlinUnit)) -> BenchmarkArguments? in
return runner.parse(args: args, benchmarksListAction: swiftLauncher.benchmarksListAction) },
collect: { (benchmarks: [BenchmarkResult], parser: ArgParser) -> Void in
runner.collect(results: benchmarks, parser: parser)
collect: { (benchmarks: [BenchmarkResult], arguments: BenchmarkArguments) -> Void in
runner.collect(results: benchmarks, arguments: arguments)
}, benchmarksListAction: swiftLauncher.benchmarksListAction)
+2
View File
@@ -43,6 +43,8 @@ if (System.getProperty("os.name") == "Mac OS X") {
}
include ':platformLibs'
include ':endorsedLibraries'
include ':endorsedLibraries:kliopt'
if (hasProperty("kotlinProjectPath")) {
include ':runtime:generator'
@@ -164,7 +164,6 @@ internal open class KonanLibrarySearchPathResolver(
if (!noStdLib) {
result.add(resolve(UnresolvedLibrary(KONAN_STDLIB_NAME, null), true))
}
if (!noDefaultLibs) {
val defaultLibs = defaultRoots.flatMap { it.listFiles }
.asSequence()
+4 -2
View File
@@ -48,8 +48,7 @@ kotlin {
}
kotlin.srcDir '../benchmarks/shared/src'
kotlin.srcDir 'src/main/kotlin'
kotlin.srcDir '../kliopt'
kotlin.srcDir '../../endorsedLibraries/kliopt/src/main/kotlin'
}
commonTest {
dependencies {
@@ -72,18 +71,21 @@ kotlin {
nativeMain {
dependsOn commonMain
kotlin.srcDir 'src/main/kotlin-native'
kotlin.srcDir '../../endorsedLibraries/kliopt/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'
}
jsMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
}
kotlin.srcDir 'src/main/kotlin-js'
kotlin.srcDir '../../endorsedLibraries/kliopt/src/main/kotlin-js'
}
linuxMain { dependsOn nativeMain }
windowsMain { dependsOn nativeMain }
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:UseExperimental(ExperimentalCli::class)
import org.jetbrains.analyzer.sendGetRequest
import org.jetbrains.analyzer.readFile
import org.jetbrains.analyzer.SummaryBenchmarksReport
@@ -103,108 +103,94 @@ fun parseNormalizeResults(results: String): Map<String, Map<String, Double>> {
return parsedNormalizeResults
}
// Prints text summary by users request.
fun summaryAction(argParser: ArgParser) {
val benchsReport = SummaryBenchmarksReport(getBenchmarkReport(argParser.get<String>("mainReport")!!, argParser.get<String>("user")))
val results = mutableListOf<String>()
val executionNormalize = argParser.get<String>("exec-normalize")?.let {
parseNormalizeResults(getFileContent(it))
}
val compileNormalize = argParser.get<String>("compile-normalize")?.let {
parseNormalizeResults(getFileContent(it))
}
val codesizeNormalize = argParser.get<String>("codesize-normalize")?.let {
parseNormalizeResults(getFileContent(it))
}
results.apply {
add(benchsReport.failedBenchmarks.size.toString())
argParser.getAll<String>("exec-samples")?. let {
val filter = if (it.first() == "all") null else it
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.EXECUTION_TIME,
argParser.get<String>("exec")!! == "geomean", filter, executionNormalize).joinToString(";"))
}
argParser.getAll<String>("compile-samples")?. let {
val filter = if (it.first() == "all") null else it
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.COMPILE_TIME,
argParser.get<String>("compile")!! == "geomean", filter, compileNormalize).joinToString(";"))
}
argParser.getAll<String>("codesize-samples")?. let {
val filter = if (it.first() == "all") null else it
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.CODE_SIZE,
argParser.get<String>("codesize")!! == "geomean", filter, codesizeNormalize).joinToString(";"))
}
}
println(results.joinToString())
}
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 = ",")
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 = ",")
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 = ",")
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")
val actions = mapOf( "summary" to Action(
::summaryAction,
ArgParser(
listOf(
OptionDescriptor(ArgType.Choice(listOf("samples", "geomean")), "exec",
description = "Execution time way of calculation", defaultValue = "geomean"),
OptionDescriptor(ArgType.String(), "exec-samples",
description = "Samples used for execution time metric (value 'all' allows use all samples)",
delimiter = ","),
OptionDescriptor(ArgType.String(), "exec-normalize",
description = "File with golden results which should be used for normalization"),
OptionDescriptor(ArgType.Choice(listOf("samples", "geomean")), "compile",
description = "Compile time way of calculation", defaultValue = "geomean"),
OptionDescriptor(ArgType.String(), "compile-samples",
description = "Samples used for compile time metric (value 'all' allows use all samples)",
delimiter = ","),
OptionDescriptor(ArgType.String(), "compile-normalize",
description = "File with golden results which should be used for normalization"),
OptionDescriptor(ArgType.Choice(listOf("samples", "geomean")), "codesize",
description = "Code size way of calculation", defaultValue = "geomean"),
OptionDescriptor(ArgType.String(), "codesize-samples",
description = "Samples used for code size metric (value 'all' allows use all samples)",
delimiter = ","),
OptionDescriptor(ArgType.String(), "codesize-normalize",
description = "File with golden results which should be used for normalization"),
OptionDescriptor(ArgType.String(), "user", "u", "User access information for authorization")
), listOf(ArgDescriptor(ArgType.String(), "mainReport", "Main report for analysis"))
)
)
)
override fun execute() {
val benchsReport = SummaryBenchmarksReport(getBenchmarkReport(mainReport!!, user))
val results = mutableListOf<String>()
val executionNormalize = execNormalize?.let {
parseNormalizeResults(getFileContent(it))
}
val compileNormalize = compileNormalize?.let {
parseNormalizeResults(getFileContent(it))
}
val codesizeNormalize = codesizeNormalize?.let {
parseNormalizeResults(getFileContent(it))
}
results.apply {
add(benchsReport.failedBenchmarks.size.toString())
val options = listOf(
OptionDescriptor(ArgType.String(), "output", "o", "Output file"),
OptionDescriptor(ArgType.Double(), "eps", "e", "Meaningful performance changes", "1.0"),
OptionDescriptor(ArgType.Boolean(), "short", "s", "Show short version of report", "false"),
OptionDescriptor(ArgType.Choice(listOf("text", "html", "teamcity", "statistics", "metrics")),
"renders", "r", "Renders for showing information", "text", isMultiple = true),
OptionDescriptor(ArgType.String(), "user", "u", "User access information for authorization")
)
val filterExec = if (execSamples.first() == "all") null else execSamples
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.EXECUTION_TIME,
exec == "geomean", filterExec, executionNormalize).joinToString(";"))
val arguments = listOf(
ArgDescriptor(ArgType.String(), "mainReport", "Main report for analysis"),
ArgDescriptor(ArgType.String(), "compareToReport", "Report to compare to", isRequired = false)
)
val filterCompile = if (compileSamples.first() == "all") null else compileSamples
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.COMPILE_TIME,
compile == "geomean", filterCompile, compileNormalize).joinToString(";"))
// Parse args.
val argParser = ArgParser(options, arguments, actions)
if (argParser.parse(args)) {
// Read contents of file.
val mainBenchsReport = getBenchmarkReport(argParser.get<String>("mainReport")!!, argParser.get<String>("user"))
var compareToBenchsReport = argParser.get<String>("compareToReport")?.let {
getBenchmarkReport(it, argParser.get<String>("user"))
val filterCodesize = if (codesizeSamples.first() == "all") null else codesizeSamples
add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.CODE_SIZE,
codesize == "geomean", filterCodesize, codesizeNormalize).joinToString(";"))
}
println(results.joinToString())
}
}
val action = Summary()
// Parse args.
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 renders = argParser.getAll<String>("renders")
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 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)
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)
var compareToBenchsReport = compareToReport?.let {
getBenchmarkReport(it, user)
}
// Generate comparasion report.
val summaryReport = SummaryBenchmarksReport(mainBenchsReport,
compareToBenchsReport,
argParser.get<Double>("eps")!!)
var output = argParser.get<String>("output")
epsValue)
var outputFile = output
renders?.forEach {
Render.getRenderByName(it).print(summaryReport, argParser.get<Boolean>("short")!!, output)
output = null
Render.getRenderByName(it).print(summaryReport, useShortForm, outputFile)
outputFile = null
}
}
}
-320
View File
@@ -1,320 +0,0 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kliopt
// Possible types of arguments.
sealed class ArgType(val hasParameter: kotlin.Boolean) {
abstract val description: kotlin.String
open fun check(value: kotlin.String, name: kotlin.String) {}
class Boolean : ArgType(false) {
override val description: kotlin.String
get() = ""
}
class String : ArgType(true) {
override val description: kotlin.String
get() = "{ String }"
}
class Int : ArgType(true) {
override val description: kotlin.String
get() = "{ Int }"
override fun check(value: kotlin.String, name: kotlin.String) {
value.toIntOrNull() ?: error("Option $name is expected to be integer number. $value is provided.")
}
}
class Double : ArgType(true) {
override val description: kotlin.String
get() = "{ Double }"
override fun check(value: kotlin.String, name: kotlin.String) {
value.toDoubleOrNull() ?: error("Option $name is expected to be double number. $value is provided.")
}
}
class Choice(val values: List<kotlin.String>) : ArgType(true) {
override val description: kotlin.String
get() = "{ Value should be one of $values }"
override fun check(value: kotlin.String, name: kotlin.String) {
if (value !in values) error("Option $name is expected to be one of $values. $value is provided.")
}
}
}
data class Action(val callback: (parser: ArgParser) -> Unit, val parser: ArgParser)
// Common descriptor both for options and positional arguments.
abstract class Descriptor(val type: ArgType,
val longName: String,
val description: String? = null,
val defaultValue: String? = null,
val isRequired: Boolean = false,
val deprecatedWarning: String? = null) {
abstract val textDescription: String
abstract val helpMessage: String
}
class OptionDescriptor(
type: ArgType,
longName: String,
val shortName: String ? = null,
description: String? = null,
defaultValue: String? = null,
isRequired: Boolean = false,
val isMultiple: Boolean = false,
val delimiter: String? = null,
deprecatedWarning: String? = null) : Descriptor (type, longName, description, defaultValue, isRequired, deprecatedWarning) {
override val textDescription: String
get() = "option -$longName"
override val helpMessage: String
get() {
val result = StringBuilder()
result.append(" -${longName}")
shortName?.let { result.append(", -$it") }
defaultValue?.let { result.append(" [$it]") }
description?.let {result.append(" -> ${it}")}
if (isRequired) result.append(" (always required)")
result.append(" ${type.description}")
deprecatedWarning?.let { result.append(" Warning: $it") }
result.append("\n")
return result.toString()
}
}
class ArgDescriptor(
type: ArgType,
longName: String,
description: String? = null,
defaultValue: String? = null,
isRequired: Boolean = true,
deprecatedWarning: String? = null) : Descriptor (type, longName, description, defaultValue, isRequired, deprecatedWarning) {
override val textDescription: String
get() = "argument $longName"
override val helpMessage: String
get() {
val result = StringBuilder()
result.append(" ${longName}")
defaultValue?.let { result.append(" [$it]") }
description?.let {result.append(" -> ${it}")}
if (!isRequired) result.append(" (optional)")
result.append(" ${type.description}")
deprecatedWarning?.let { result.append(" Warning: $it") }
result.append("\n")
return result.toString()
}
}
// Arguments parser.
class ArgParser(optionsList: List<OptionDescriptor>, argsList: List<ArgDescriptor> = listOf<ArgDescriptor>(),
val actions: Map<String, Action> = emptyMap(), useDefaultHelpShortName: Boolean = true) {
private val options = optionsList.union(if (useDefaultHelpShortName)
listOf(OptionDescriptor(ArgType.Boolean(), "help", "h", "Usage info"))
else
listOf(OptionDescriptor(ArgType.Boolean(), "help", description = "Usage info"))
).toList()
private val arguments = argsList
private lateinit var parsedValues: MutableMap<String, ParsedArg?>
private lateinit var valuesOrigin: MutableMap<String, ValueOrigin?>
enum class ValueOrigin { SET_BY_USER, SET_DEFAULT_VALUE, UNSET }
inner class ParsedArg(val descriptor: Descriptor, val values: List<String>) {
init {
// Check correctness of initialization data.
if (values.isEmpty()) {
printError("Parsed value should be provided!")
}
}
// Get value of argument converted to expected type.
private fun <T : Any> getTyped(value: String): T {
val typedValue = when (descriptor.type) {
is ArgType.Int -> value.toInt()
is ArgType.Double -> value.toDouble()
is ArgType.Boolean -> value == "true"
else -> value
} as? T
typedValue ?: printError("Argument ${descriptor.longName} has type ${descriptor.type} which differs from expected!")
return typedValue
}
// Get value of argument.
fun <T : Any> get(): T {
return getTyped<T>(values[0])
}
// Get all values of argument.
// For options that can be set multiple types.
fun <T : Any> getAll(): List<T> {
return values.map { getTyped<T>(it) }
}
}
// Output error. Also adds help usage information for easy understanding of problem.
fun printError(message: String): Nothing {
error("$message\n${makeUsage()}")
}
// Get origin of option value.
fun getOrigin(name: String) = valuesOrigin[name] ?: printError("No option/argument $name in list of avaliable options")
private fun saveAsArg(argDescriptors: Map<String, ArgDescriptor>, arg: String, processedValues: Map<String, MutableList<String>>): Boolean {
// Find uninitialized arguments.
val nullArgs = argDescriptors.keys.filter { processedValues.getValue(it).isEmpty() }
val name = nullArgs.firstOrNull()
name?. let {
argDescriptors.getValue(name).type.check(arg, name)
argDescriptors.getValue(name).deprecatedWarning?.let { println ("Warning: $it") }
processedValues.getValue(name).add(arg)
return true
}
return false
}
private fun saveAsOption(descriptor: OptionDescriptor, value: String, processedValues: Map<String, MutableList<String>>) {
if (!descriptor.isMultiple && !processedValues.getValue(descriptor.longName).isEmpty()) {
printError("Option ${descriptor.longName} is used more than one time!")
}
descriptor.deprecatedWarning?.let { if (processedValues.getValue(descriptor.longName).isEmpty()) println ("Warning: $it") }
val savedValues = descriptor.delimiter?.let { value.split(it) } ?: listOf(value)
savedValues.forEach {
descriptor.type.check(it, descriptor.longName)
processedValues.getValue(descriptor.longName).add(it)
}
}
// Parse arguments.
// Returns true if all arguments were parsed, otherwise return false and print help message.
fun parse(args: Array<String>): Boolean {
var index = 0
val optDescriptors = options.map { it.longName to it }.toMap()
val shortNames = options.filter { it.shortName != null }.map { it.shortName!! to it.longName }.toMap()
val argDescriptors = arguments.map { it.longName to it }.toMap()
val descriptorsKeys = optDescriptors.keys.union(argDescriptors.keys).toList()
val processedValues = descriptorsKeys.map { it to mutableListOf<String>() }.toMap().toMutableMap()
parsedValues = descriptorsKeys.map { it to null }.toMap().toMutableMap()
valuesOrigin = descriptorsKeys.map { it to ValueOrigin.UNSET }.toMap().toMutableMap()
while (index < args.size) {
val arg = args[index]
// Check for actions.
actions.forEach { (name, action) ->
if (arg == name) {
// Use parser for this action.
val parseResult = action.parser.parse(args.slice(index + 1..args.size - 1).toTypedArray())
if (parseResult)
action.callback(action.parser)
return false
}
}
if (arg.startsWith('-')) {
// Option is found.
val name = shortNames[arg.substring(1)] ?: arg.substring(1)
val descriptor = optDescriptors[name]
descriptor?. let {
if (descriptor.type.hasParameter) {
if (index < args.size - 1) {
saveAsOption(descriptor, args[index + 1], processedValues)
index++
} else {
// An error, option with value without value.
printError("No value for ${descriptor.textDescription}")
}
} else {
if (name == "help") {
println(makeUsage())
return false
}
saveAsOption (descriptor, "true", processedValues)
}
} ?: run {
// Try save as argument.
if (!saveAsArg(argDescriptors, arg, processedValues)) {
printError("Unknown option $arg")
}
}
} else {
// Argument is found.
if (!saveAsArg(argDescriptors, arg, processedValues)) {
printError("Too many arguments! Couldn't proccess argument $arg!")
}
}
index++
}
processedValues.forEach { (key, value) ->
val descriptor = optDescriptors[key] ?: argDescriptors.getValue(key)
// Not inited, append default value if needed.
if (value.isEmpty()) {
descriptor.defaultValue?. let {
parsedValues[key] = ParsedArg(descriptor, listOf(descriptor.defaultValue))
valuesOrigin[key] = ValueOrigin.SET_DEFAULT_VALUE
} ?: run {
if (descriptor.isRequired) {
printError("Please, provide value for ${descriptor.textDescription}. It should be always set")
} else {
parsedValues[key] = null
}
}
} else {
parsedValues[key] = ParsedArg(descriptor, value)
valuesOrigin[key] = ValueOrigin.SET_BY_USER
}
}
return true
}
// Get value of argument.
fun <T : Any> get(name: String): T? {
if (::parsedValues.isInitialized) {
val arg = parsedValues[name]
return arg?.get()
} else {
printError("Method parse() of ArgParser class should be called before getting arguments and options.")
}
}
// Get all values of argument.
// For options that can be set multiple types.
fun <T : Any> getAll(name: String): List<T>? {
if (::parsedValues.isInitialized) {
val arg = parsedValues[name]
return arg?.getAll()
} else {
printError("Method parse() of ArgParser class should be called before getting arguments and options.")
}
}
private fun makeUsage(): String {
val result = StringBuilder()
result.append("Usage: \n")
if (!arguments.isEmpty()) {
result.append("Arguments: \n")
arguments.forEach {
result.append(it.helpMessage)
}
}
result.append("Options: \n")
options.forEach {
result.append(it.helpMessage)
}
return result.toString()
}
}
@@ -9,24 +9,19 @@ import org.jetbrains.kotlin.konan.target.PlatformManager
import org.jetbrains.kotlin.konan.library.*
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.native.interop.gen.jvm.interop
import org.jetbrains.kliopt.ArgParser
import org.jetbrains.kotlin.konan.CURRENT
import org.jetbrains.kotlin.konan.KonanVersion
import org.jetbrains.kotlin.library.toUnresolvedLibraries
import org.jetbrains.kotlin.native.interop.tool.*
// TODO: this function should eventually be eliminated from 'utilities'.
// The interaction of interop and the compler should be streamlined.
// The interaction of interop and the compiler should be streamlined.
fun invokeInterop(flavor: String, args: Array<String>): Array<String>? {
val argParser = ArgParser(if (flavor == "native") getCInteropArguments() else getJSInteropArguments(),
useDefaultHelpShortName = false)
if (!argParser.parse(args))
return null
val outputFileName = argParser.get<String>("output")!!
val noDefaultLibs = argParser.get<Boolean>(NODEFAULTLIBS)!!
val purgeUserLibs = argParser.get<Boolean>(PURGE_USER_LIBS)!!
val temporaryFilesDir = argParser.get<String>(TEMP_DIR)
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 purgeUserLibs = arguments.purgeUserLibs
val temporaryFilesDir = arguments.tempDir
val buildDir = File("$outputFileName-build")
val generatedDir = File(buildDir, "kotlin")
@@ -40,9 +35,10 @@ fun invokeInterop(flavor: String, args: Array<String>): Array<String>? {
val additionalProperties = mutableMapOf<String, Any>(
"manifest" to manifest.path)
val cstubsName ="cstubs"
val libraries = argParser.getAll<String>("library") ?: listOf<String>()
val repos = argParser.getAll<String>("repo") ?: listOf<String>()
val targetRequest = argParser.get<String>("target")!!
val libraries = arguments.library
val repos = arguments.repo
val targetRequest = if (arguments is CInteropArguments) arguments.target
else (arguments as JSInteropArguments).target
val target = PlatformManager().targetManager(targetRequest).target
if (flavor == "native") {
@@ -66,7 +62,7 @@ fun invokeInterop(flavor: String, args: Array<String>): Array<String>? {
additionalProperties.putAll(mapOf("cstubsname" to cstubsName, "import" to imports))
}
val cinteropArgsToCompiler = interop(flavor, args + additionalArgs, additionalProperties) ?: return null
val cinteropArgsToCompiler = interop(flavor, args + additionalArgs, additionalProperties)
val nativeStubs =
if (flavor == "wasm")