diff --git a/HACKING.md b/HACKING.md index 7e6c41ffd7d..439b86c27e2 100644 --- a/HACKING.md +++ b/HACKING.md @@ -135,9 +135,9 @@ To update the blackbox compiler tests set TeamCity build number in `gradle.prope cd tools/benchmarksAnalyzer/build/bin//benchmarksAnalyzerReleaseExecutable/ ./benchmarksAnalyzer.kexe - 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. diff --git a/Interop/StubGenerator/build.gradle b/Interop/StubGenerator/build.gradle index 0ed363c5d95..b3d7a6f735d 100644 --- a/Interop/StubGenerator/build.gradle +++ b/Interop/StubGenerator/build.gradle @@ -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'] } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/defFileDependencies.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/defFileDependencies.kt index c2ed44cd4f8..ba520573cd5 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/defFileDependencies.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/defFileDependencies.kt @@ -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, defFiles: List) private fun makeDependencyAssignerForTarget(target: String, defFiles: List): SingleTargetDependencyAssigner { val tool = prepareTool(target, KotlinPlatform.NATIVE) - val argParser = ArgParser(getCInteropArguments(), useDefaultHelpShortName = false) - argParser.parse(arrayOf()) + val cinteropArguments = CInteropArguments() + cinteropArguments.argParser.parse(arrayOf()) val libraries = defFiles.associateWith { buildNativeLibrary( tool, DefFile(it, tool.substitutions), - argParser, + cinteropArguments, ImportsImpl(emptyMap()) ).getHeaderPaths() } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt index 731b47413f0..6274dc71e65 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt @@ -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 { - 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 { - 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(propertyName) ?: listOf()).toTypedArray() diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index e3c3d05faa5..4a2b181a071 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -158,35 +158,34 @@ private fun findFilesByGlobs(roots: List, globs: List): Map, additionalArgs: Map = mapOf()): Array? { - val argParser = ArgParser(getCInteropArguments(), useDefaultHelpShortName = false) - if (!argParser.parse(args)) - return null - val ktGenRoot = argParser.get("generated") - val nativeLibsDir = argParser.get("natives") - val flavorName = argParser.get("flavor") +private fun processCLib(args: Array, additionalArgs: Map = mapOf()): Array { + 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("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("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("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("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, additionalArgs: Map = def.config.linkerOpts.toTypedArray() + tool.defaultCompilerOpts + additionalLinkerOpts - val linkerName = argParser.get("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("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, additionalArgs: Map = val libName = (additionalArgs["cstubsname"] as? String)?: fqParts.joinToString("") + "stubs" - val tempFiles = TempFiles(libName, argParser.get("Xtemporary-files-dir")) + val tempFiles = TempFiles(libName, cinteropArguments.tempDir) val imports = parseImports((additionalArgs["import"] as? List)?.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 -> { diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/StubGenerator.kt index c0e8f19293d..f2569a234a1 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/StubGenerator.kt @@ -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): String = const val idlMathPackage = "kotlinx.interop.wasm.math" const val idlDomPackage = "kotlinx.interop.wasm.dom" -fun processIdlLib(args: Array, additionalArgs: Map = mapOf()): Array? { - val argParser = ArgParser(getJSInteropArguments(), useDefaultHelpShortName = false) - if (!argParser.parse(args)) - return null +fun processIdlLib(args: Array, additionalArgs: Map = mapOf()): Array { + val jsInteropArguments = JSInteropArguments() + jsInteropArguments.argParser.parse(args) // TODO: Refactor me. - val ktGenRoot = File(argParser.get("generated")!!).mkdirs() - val nativeLibsDir = File(argParser.get("natives")!!).mkdirs() + val ktGenRoot = File(jsInteropArguments.generated).mkdirs() + val nativeLibsDir = File(jsInteropArguments.natives).mkdirs() - val idl = when (argParser.get("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("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()) } diff --git a/build-tools/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy b/build-tools/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy index 98c609e30ca..701ddc9cf97 100644 --- a/build-tools/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy +++ b/build-tools/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy @@ -301,6 +301,7 @@ class NativeInteropPlugin implements Plugin { prj.dependencies { interopStubGenerator project(path: ":Interop:StubGenerator") + interopStubGenerator project(path: ":endorsedLibraries:kliopt", configuration: "jvmRuntimeElements") } // FIXME: choose tasks more wisely diff --git a/build-tools/src/main/kotlin/org/jetbrains/kotlin/BuildRegister.kt b/build-tools/src/main/kotlin/org/jetbrains/kotlin/BuildRegister.kt index 3d2611cdad6..7b60e5d3970 100644 --- a/build-tools/src/main/kotlin/org/jetbrains/kotlin/BuildRegister.kt +++ b/build-tools/src/main/kotlin/org/jetbrains/kotlin/BuildRegister.kt @@ -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(',') diff --git a/build.gradle b/build.gradle index e1b7f23a4c6..c07010742fe 100644 --- a/build.gradle +++ b/build.gradle @@ -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) { diff --git a/cmd/run_konan b/cmd/run_konan index a0b559c3639..99db30dc20a 100755 --- a/cmd/run_konan +++ b/cmd/run_konan @@ -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 \ diff --git a/cmd/run_konan.bat b/cmd/run_konan.bat index dc9bd46b40b..3bc65beb458 100644 --- a/cmd/run_konan.bat +++ b/cmd/run_konan.bat @@ -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 ^ diff --git a/endorsedLibraries/build.gradle b/endorsedLibraries/build.gradle new file mode 100644 index 00000000000..fc928fa4973 --- /dev/null +++ b/endorsedLibraries/build.gradle @@ -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") + } + } + } +} diff --git a/endorsedLibraries/kliopt/build.gradle b/endorsedLibraries/kliopt/build.gradle new file mode 100644 index 00000000000..93698b2d0bf --- /dev/null +++ b/endorsedLibraries/kliopt/build.gradle @@ -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")) + } +} \ No newline at end of file diff --git a/endorsedLibraries/kliopt/gradle.properties b/endorsedLibraries/kliopt/gradle.properties new file mode 100644 index 00000000000..23a0cc91ff7 --- /dev/null +++ b/endorsedLibraries/kliopt/gradle.properties @@ -0,0 +1,2 @@ +org.jetbrains.kotlin.native.home=../../dist +org.jetbrains.kotlin.native.jvmArgs=-Xmx6G \ No newline at end of file diff --git a/endorsedLibraries/kliopt/src/main/kotlin-js/org/jetbrains/kliopt/KliOpt.kt b/endorsedLibraries/kliopt/src/main/kotlin-js/org/jetbrains/kliopt/KliOpt.kt new file mode 100644 index 00000000000..97d4403f396 --- /dev/null +++ b/endorsedLibraries/kliopt/src/main/kotlin-js/org/jetbrains/kliopt/KliOpt.kt @@ -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!") +} \ No newline at end of file diff --git a/endorsedLibraries/kliopt/src/main/kotlin-jvm/org/jetbrains/kliopt/KliOpt.kt b/endorsedLibraries/kliopt/src/main/kotlin-jvm/org/jetbrains/kliopt/KliOpt.kt new file mode 100644 index 00000000000..b09955400bf --- /dev/null +++ b/endorsedLibraries/kliopt/src/main/kotlin-jvm/org/jetbrains/kliopt/KliOpt.kt @@ -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) +} + diff --git a/endorsedLibraries/kliopt/src/main/kotlin-native/org/jetbrains/kliopt/KliOpt.kt b/endorsedLibraries/kliopt/src/main/kotlin-native/org/jetbrains/kliopt/KliOpt.kt new file mode 100644 index 00000000000..29643db35c4 --- /dev/null +++ b/endorsedLibraries/kliopt/src/main/kotlin-native/org/jetbrains/kliopt/KliOpt.kt @@ -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) +} \ No newline at end of file diff --git a/endorsedLibraries/kliopt/src/main/kotlin/org/jetbrains/kliopt/ArgType.kt b/endorsedLibraries/kliopt/src/main/kotlin/org/jetbrains/kliopt/ArgType.kt new file mode 100644 index 00000000000..895d35ec32a --- /dev/null +++ b/endorsedLibraries/kliopt/src/main/kotlin/org/jetbrains/kliopt/ArgType.kt @@ -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(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(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(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(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(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) : ArgType(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") } + } +} \ No newline at end of file diff --git a/endorsedLibraries/kliopt/src/main/kotlin/org/jetbrains/kliopt/ExperimentalCli.kt b/endorsedLibraries/kliopt/src/main/kotlin/org/jetbrains/kliopt/ExperimentalCli.kt new file mode 100644 index 00000000000..60a39f026f7 --- /dev/null +++ b/endorsedLibraries/kliopt/src/main/kotlin/org/jetbrains/kliopt/ExperimentalCli.kt @@ -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 \ No newline at end of file diff --git a/endorsedLibraries/kliopt/src/main/kotlin/org/jetbrains/kliopt/KliOpt.kt b/endorsedLibraries/kliopt/src/main/kotlin/org/jetbrains/kliopt/KliOpt.kt new file mode 100644 index 00000000000..0fa7c1408fd --- /dev/null +++ b/endorsedLibraries/kliopt/src/main/kotlin/org/jetbrains/kliopt/KliOpt.kt @@ -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>) { + /** + * 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(val type: ArgType, + val fullName: String, + val description: String? = null, + val defaultValue: List = 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>() + /** + * Map of arguments: key - fullname of argument, value - pair of descriptor and parsed values. + */ + protected val arguments = mutableMapOf>() + /** + * Map of subcommands. + */ + @UseExperimental(ExperimentalCli::class) + protected val subcommands = mutableMapOf() + + /** + * Mapping for short options names for quick search. + */ + private lateinit var shortNames: Map> + + /** + * 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(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( + type: ArgType, + fullName: String, + val shortName: String ? = null, + description: String? = null, + defaultValue: List = emptyList(), + required: Boolean = false, + val multiple: Boolean = false, + val delimiter: String? = null, + deprecatedWarning: String? = null) : Descriptor (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( + type: ArgType, + fullName: String, + val number: Int? = null, + description: String? = null, + defaultValue: List = emptyList(), + required: Boolean = true, + deprecatedWarning: String? = null) : Descriptor (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(val type: ArgType, + 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 { + 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(val type: ArgType, + 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 { + 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(val type: ArgType, + val fullName: String? = null, + val shortName: String ? = null, + val description: String? = null, + val defaultValue: List = 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> { + 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 option(type: ArgType, + 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 option(type: ArgType, + 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 options(type: ArgType, + fullName: String? = null, + shortName: String ? = null, + description: String? = null, + defaultValue: List = 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(val type: ArgType, + val fullName: String? = null, + val description: String? = null, + val required: Boolean = true, + val deprecatedWarning: String? = null) { + operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueInterface { + 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(val type: ArgType, + 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 { + 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(val type: ArgType, + val fullName: String? = null, + val number: Int? = null, + val description: String? = null, + val defaultValue: List = emptyList(), + val required: Boolean = true, + val deprecatedWarning: String? = null) { + operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgumentValueInterface> { + 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 argument(type: ArgType, + 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 argument(type: ArgType, + 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 arguments(type: ArgType, + fullName: String? = null, + number: Int? = null, + description: String? = null, + defaultValue: List = 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 arguments(type: ArgType, + description: String? = null, + defaultValue: List = emptyList(), + required: Boolean = true, + deprecatedWarning: String? = null): ArgumentValueInterface> { + 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(val descriptor: Descriptor, val argumentValue: ArgumentValue) { + + /** + * Add parsed value from command line. + */ + fun addValue(stringValue: String, + setValue: ArgumentValue.(String, String) -> Unit = ArgumentValue::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::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 { + operator fun getValue(thisRef: Any?, property: KProperty<*>): T + } + + /** + * Argument/option value. + */ + abstract class ArgumentValue(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(conversion: (value: String, name: String, helpMessage: String)->T): + ArgumentValue(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(conversion: (value: String, name: String, helpMessage: String)->T): + ArgumentSingleValue(conversion), ArgumentValueInterface { + 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(conversion: (value: String, name: String, helpMessage: String)->T): + ArgumentSingleValue(conversion), ArgumentValueInterface { + 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(conversion: (value: String, name: String, helpMessage: String)->T): + ArgumentValue> ( + { value, name, _ -> mutableListOf(conversion(value, name, makeUsage())) } + ), ArgumentValueInterface> { + + init { + values = mutableListOf() + } + + override operator fun getValue(thisRef: Any?, property: KProperty<*>): MutableList = 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 saveAsOption(parsingValue: ParsingValue, 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): 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() + } +} \ No newline at end of file diff --git a/endorsedLibraries/kliopt/src/tests/ArgumentsTests.kt b/endorsedLibraries/kliopt/src/tests/ArgumentsTests.kt new file mode 100644 index 00000000000..eb0d5a18d92 --- /dev/null +++ b/endorsedLibraries/kliopt/src/tests/ArgumentsTests.kt @@ -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) + } +} diff --git a/endorsedLibraries/kliopt/src/tests/ErrorTests.kt b/endorsedLibraries/kliopt/src/tests/ErrorTests.kt new file mode 100644 index 00000000000..600895455e6 --- /dev/null +++ b/endorsedLibraries/kliopt/src/tests/ErrorTests.kt @@ -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 { 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 { + 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 { + 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 { + argParser.parse(arrayOf("-r", "xml")) + } + assertTrue("Option renders is expected to be one of [text, html]. xml is provided." in exception.message!!) + } +} diff --git a/endorsedLibraries/kliopt/src/tests/HelpTests.kt b/endorsedLibraries/kliopt/src/tests/HelpTests.kt new file mode 100644 index 00000000000..75542574f0e --- /dev/null +++ b/endorsedLibraries/kliopt/src/tests/HelpTests.kt @@ -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) + } +} diff --git a/endorsedLibraries/kliopt/src/tests/OptionsTests.kt b/endorsedLibraries/kliopt/src/tests/OptionsTests.kt new file mode 100644 index 00000000000..ee7e4c3dffe --- /dev/null +++ b/endorsedLibraries/kliopt/src/tests/OptionsTests.kt @@ -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]) + } +} diff --git a/endorsedLibraries/kliopt/src/tests/SubcommandsTests.kt b/endorsedLibraries/kliopt/src/tests/SubcommandsTests.kt new file mode 100644 index 00000000000..844e4db7654 --- /dev/null +++ b/endorsedLibraries/kliopt/src/tests/SubcommandsTests.kt @@ -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) + } +} diff --git a/performance/build.gradle b/performance/build.gradle index 1d072945a18..34b53cbb1ac 100644 --- a/performance/build.gradle +++ b/performance/build.gradle @@ -170,5 +170,5 @@ task swiftinterop { task videoplayer { dependsOn 'clean' - dependsOn 'swiftinterop:konanRun' + dependsOn 'videoplayer:konanRun' } \ No newline at end of file diff --git a/performance/cinterop/build.gradle.kts b/performance/cinterop/build.gradle.kts index 79e6b4568d6..f7799d6f34c 100644 --- a/performance/cinterop/build.gradle.kts +++ b/performance/cinterop/build.gradle.kts @@ -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") } diff --git a/performance/cinterop/src/main/kotlin/main.kt b/performance/cinterop/src/main/kotlin/main.kt index 3794f5f2478..80671c45eb0 100644 --- a/performance/cinterop/src/main/kotlin/main.kt +++ b/performance/cinterop/src/main/kotlin/main.kt @@ -39,8 +39,10 @@ class CinteropLauncher : Launcher() { fun main(args: Array) { val launcher = CinteropLauncher() - BenchmarksRunner.runBenchmarks(args, { parser: ArgParser -> - launcher.launch(parser.get("warmup")!!, parser.get("repeat")!!, parser.get("prefix")!!, - parser.getAll("filter"), parser.getAll("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) } \ No newline at end of file diff --git a/performance/framework/build.gradle b/performance/framework/build.gradle index 40cd5a5ac75..56f71211646 100644 --- a/performance/framework/build.gradle +++ b/performance/framework/build.gradle @@ -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" } } diff --git a/performance/objcinterop/build.gradle.kts b/performance/objcinterop/build.gradle.kts index f85b68a5547..54ba83cf82c 100644 --- a/performance/objcinterop/build.gradle.kts +++ b/performance/objcinterop/build.gradle.kts @@ -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") diff --git a/performance/objcinterop/src/main/kotlin/main.kt b/performance/objcinterop/src/main/kotlin/main.kt index 5091c61778f..444361da9d8 100644 --- a/performance/objcinterop/src/main/kotlin/main.kt +++ b/performance/objcinterop/src/main/kotlin/main.kt @@ -26,8 +26,10 @@ class ObjCInteropLauncher: Launcher() { fun main(args: Array) { val launcher = ObjCInteropLauncher() - BenchmarksRunner.runBenchmarks(args, { parser: ArgParser -> - launcher.launch(parser.get("warmup")!!, parser.get("repeat")!!, parser.get("prefix")!!, - parser.getAll("filter"), parser.getAll("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) } \ No newline at end of file diff --git a/performance/ring/build.gradle.kts b/performance/ring/build.gradle.kts index cc89ab3b036..01b905285ff 100644 --- a/performance/ring/build.gradle.kts +++ b/performance/ring/build.gradle.kts @@ -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") } diff --git a/performance/ring/src/main/kotlin/main.kt b/performance/ring/src/main/kotlin/main.kt index ff96f5ffd89..2c79113d49f 100644 --- a/performance/ring/src/main/kotlin/main.kt +++ b/performance/ring/src/main/kotlin/main.kt @@ -209,8 +209,10 @@ class RingLauncher : Launcher() { fun main(args: Array) { val launcher = RingLauncher() - BenchmarksRunner.runBenchmarks(args, { parser: ArgParser -> - launcher.launch(parser.get("warmup")!!, parser.get("repeat")!!, parser.get("prefix")!!, - parser.getAll("filter"), parser.getAll("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) } \ No newline at end of file diff --git a/performance/shared/src/main/kotlin/org/jetbrains/benchmarksLauncher/launcher.kt b/performance/shared/src/main/kotlin/org/jetbrains/benchmarksLauncher/launcher.kt index 64df7feb026..a39e2ccde8d 100644 --- a/performance/shared/src/main/kotlin/org/jetbrains/benchmarksLauncher/launcher.kt +++ b/performance/shared/src/main/kotlin/org/jetbrains/benchmarksLauncher/launcher.kt @@ -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, benchmarksListAction: (ArgParser)->Unit): ArgParser? { - val actions = mapOf("list" to Action( - benchmarksListAction, - ArgParser(listOf())) - ) - 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, 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, parser: ArgParser) { - JsonReportCreator(results).printJsonReport(parser.get("output")) + fun collect(results: List, arguments: BenchmarkArguments) { + if (arguments is BaseBenchmarkArguments) { + JsonReportCreator(results).printJsonReport(arguments.output) + } } fun runBenchmarks(args: Array, - run: (parser: ArgParser) -> List, - parseArgs: (args: Array, benchmarksListAction: (ArgParser)->Unit) -> ArgParser? = this::parse, - collect: (results: List, 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, + parseArgs: (args: Array, benchmarksListAction: ()->Unit) -> BenchmarkArguments? = this::parse, + collect: (results: List, arguments: BenchmarkArguments) -> Unit = this::collect, + benchmarksListAction: ()->Unit) { + val arguments = parseArgs(args, benchmarksListAction) + arguments?.let { + val results = run(arguments) + collect(results, arguments) } } } diff --git a/performance/swiftinterop/build.gradle b/performance/swiftinterop/build.gradle index 2a3ce855ebb..2845be072ae 100644 --- a/performance/swiftinterop/build.gradle +++ b/performance/swiftinterop/build.gradle @@ -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" } } diff --git a/performance/swiftinterop/swiftSrc/main.swift b/performance/swiftinterop/swiftSrc/main.swift index 1d720af81fe..849c4c706f5 100644 --- a/performance/swiftinterop/swiftSrc/main.swift +++ b/performance/swiftinterop/swiftSrc/main.swift @@ -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) \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 0340ff9d754..e2254cb90c9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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' diff --git a/shared/src/library/kotlin/org/jetbrains/kotlin/konan/library/SearchPathResolver.kt b/shared/src/library/kotlin/org/jetbrains/kotlin/konan/library/SearchPathResolver.kt index 25180386fb7..7a000d50b85 100644 --- a/shared/src/library/kotlin/org/jetbrains/kotlin/konan/library/SearchPathResolver.kt +++ b/shared/src/library/kotlin/org/jetbrains/kotlin/konan/library/SearchPathResolver.kt @@ -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() diff --git a/tools/benchmarksAnalyzer/build.gradle b/tools/benchmarksAnalyzer/build.gradle index 2f1c796a3f0..82f60503b51 100644 --- a/tools/benchmarksAnalyzer/build.gradle +++ b/tools/benchmarksAnalyzer/build.gradle @@ -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 } diff --git a/tools/benchmarksAnalyzer/src/main/kotlin/main.kt b/tools/benchmarksAnalyzer/src/main/kotlin/main.kt index 72c0bd17692..db783276ce0 100644 --- a/tools/benchmarksAnalyzer/src/main/kotlin/main.kt +++ b/tools/benchmarksAnalyzer/src/main/kotlin/main.kt @@ -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> { return parsedNormalizeResults } -// Prints text summary by users request. -fun summaryAction(argParser: ArgParser) { - val benchsReport = SummaryBenchmarksReport(getBenchmarkReport(argParser.get("mainReport")!!, argParser.get("user"))) - val results = mutableListOf() - val executionNormalize = argParser.get("exec-normalize")?.let { - parseNormalizeResults(getFileContent(it)) - } - val compileNormalize = argParser.get("compile-normalize")?.let { - parseNormalizeResults(getFileContent(it)) - } - val codesizeNormalize = argParser.get("codesize-normalize")?.let { - parseNormalizeResults(getFileContent(it)) - } - results.apply { - add(benchsReport.failedBenchmarks.size.toString()) - argParser.getAll("exec-samples")?. let { - val filter = if (it.first() == "all") null else it - add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.EXECUTION_TIME, - argParser.get("exec")!! == "geomean", filter, executionNormalize).joinToString(";")) - } - argParser.getAll("compile-samples")?. let { - val filter = if (it.first() == "all") null else it - add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.COMPILE_TIME, - argParser.get("compile")!! == "geomean", filter, compileNormalize).joinToString(";")) - } - argParser.getAll("codesize-samples")?. let { - val filter = if (it.first() == "all") null else it - add(benchsReport.getResultsByMetric(BenchmarkResult.Metric.CODE_SIZE, - argParser.get("codesize")!! == "geomean", filter, codesizeNormalize).joinToString(";")) - } - } - println(results.joinToString()) -} - fun main(args: Array) { + 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() + 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("mainReport")!!, argParser.get("user")) - var compareToBenchsReport = argParser.get("compareToReport")?.let { - getBenchmarkReport(it, argParser.get("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("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("eps")!!) - - var output = argParser.get("output") + epsValue) + var outputFile = output renders?.forEach { - Render.getRenderByName(it).print(summaryReport, argParser.get("short")!!, output) - output = null + Render.getRenderByName(it).print(summaryReport, useShortForm, outputFile) + outputFile = null } } } \ No newline at end of file diff --git a/tools/kliopt/org/jetbrains/kliopt/KliOpt.kt b/tools/kliopt/org/jetbrains/kliopt/KliOpt.kt deleted file mode 100644 index ba488770257..00000000000 --- a/tools/kliopt/org/jetbrains/kliopt/KliOpt.kt +++ /dev/null @@ -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) : 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, argsList: List = listOf(), - val actions: Map = 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 - private lateinit var valuesOrigin: MutableMap - - enum class ValueOrigin { SET_BY_USER, SET_DEFAULT_VALUE, UNSET } - - inner class ParsedArg(val descriptor: Descriptor, val values: List) { - 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 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 get(): T { - return getTyped(values[0]) - } - - // Get all values of argument. - // For options that can be set multiple types. - fun getAll(): List { - return values.map { getTyped(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, arg: String, processedValues: Map>): 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>) { - 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): 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() }.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 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 getAll(name: String): List? { - 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() - } -} \ No newline at end of file diff --git a/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/InteropCompiler.kt b/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/InteropCompiler.kt index ab386ebf2b3..cf38cdedad2 100644 --- a/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/InteropCompiler.kt +++ b/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/InteropCompiler.kt @@ -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): Array? { - val argParser = ArgParser(if (flavor == "native") getCInteropArguments() else getJSInteropArguments(), - useDefaultHelpShortName = false) - if (!argParser.parse(args)) - return null - val outputFileName = argParser.get("output")!! - val noDefaultLibs = argParser.get(NODEFAULTLIBS)!! - val purgeUserLibs = argParser.get(PURGE_USER_LIBS)!! - val temporaryFilesDir = argParser.get(TEMP_DIR) +fun invokeInterop(flavor: String, args: Array): Array { + 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): Array? { val additionalProperties = mutableMapOf( "manifest" to manifest.path) val cstubsName ="cstubs" - val libraries = argParser.getAll("library") ?: listOf() - val repos = argParser.getAll("repo") ?: listOf() - val targetRequest = argParser.get("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): Array? { 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")