From 1832f5a3f7153e14f8fe09e92d2eff78f0504488 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Sat, 18 Nov 2023 11:01:34 +0000 Subject: [PATCH] [K/JS] Introduce the ability to consume platform-specific cli arguments inside the main function ^KT-16981 Fixed --- .../K2JSCompilerArgumentsCopyGenerated.kt | 1 + .../common/arguments/K2JSCompilerArguments.kt | 10 ++++++++++ .../jetbrains/kotlin/cli/js/K2JsIrCompiler.kt | 5 ++++- .../kotlin/ir/backend/js/JsIrBackendContext.kt | 1 + .../ir/backend/js/ic/HashCalculatorForIC.kt | 8 +++++++- .../lower/MainFunctionCallWrapperLowering.kt | 6 +++++- compiler/testData/cli/js/jsExtraHelp.out | 2 ++ .../kotlin/js/config/JSConfigurationKeys.java | 4 ++++ .../kotlin/gradle/Kotlin2JsGradlePluginIT.kt | 18 ++++++++++++++++++ .../kotlin-js-nodejs-project/build.gradle | 1 + .../src/main/kotlin/Base.kt | 4 ++++ .../ExperimentalMainFunctionArgumentsDsl.kt | 10 ++++++++++ .../gradle/targets/js/dsl/KotlinJsTargetDsl.kt | 9 +++++++++ .../gradle/targets/js/ir/KotlinJsIrTarget.kt | 13 +++++++++++++ .../gradle/targets/js/ir/KotlinNodeJsIr.kt | 4 ++++ 15 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/ExperimentalMainFunctionArgumentsDsl.kt diff --git a/compiler/cli/cli-common/gen/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArgumentsCopyGenerated.kt b/compiler/cli/cli-common/gen/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArgumentsCopyGenerated.kt index 9006f516cce..19b883b6d08 100644 --- a/compiler/cli/cli-common/gen/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArgumentsCopyGenerated.kt +++ b/compiler/cli/cli-common/gen/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArgumentsCopyGenerated.kt @@ -57,6 +57,7 @@ fun copyK2JSCompilerArguments(from: K2JSCompilerArguments, to: K2JSCompilerArgum to.outputPrefix = from.outputPrefix to.partialLinkageLogLevel = from.partialLinkageLogLevel to.partialLinkageMode = from.partialLinkageMode + to.platformArgumentsProviderJsExpression = from.platformArgumentsProviderJsExpression to.sourceMap = from.sourceMap to.sourceMapBaseDirs = from.sourceMapBaseDirs to.sourceMapEmbedSources = from.sourceMapEmbedSources diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index b6f903f9fa0..107b38aabf9 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -494,6 +494,16 @@ In combination with '-meta-info', this generates both IR and pre-IR versions of field = value } + @Argument( + value = "-Xplatform-arguments-in-main-function", + description = "JS expression that will be executed in runtime and be put as an Array parameter of the main function" + ) + var platformArgumentsProviderJsExpression: String? = null + set(value) { + checkFrozen() + field = value + } + @GradleOption( value = DefaultValue.BOOLEAN_TRUE_DEFAULT, gradleInputType = GradleInputTypes.INPUT, diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index a6ef11e50ef..679393062b7 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -23,7 +23,6 @@ import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.RUNTIME_DIAGNOSTIC_EXCEPTION import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.RUNTIME_DIAGNOSTIC_LOG import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot -import org.jetbrains.kotlin.cli.common.fir.reportToMessageCollector import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.* @@ -211,6 +210,10 @@ class K2JsIrCompiler : CLICompiler() { configurationJs.put(JSConfigurationKeys.GENERATE_DTS, arguments.generateDts) configurationJs.put(JSConfigurationKeys.GENERATE_INLINE_ANONYMOUS_FUNCTIONS, arguments.irGenerateInlineAnonymousFunctions) + arguments.platformArgumentsProviderJsExpression?.let { + configurationJs.put(JSConfigurationKeys.DEFINE_PLATFORM_MAIN_FUNCTION_ARGUMENTS, it) + } + val zipAccessor = DisposableZipFileSystemAccessor(64) Disposer.register(rootDisposable, zipAccessor) configurationJs.put(JSConfigurationKeys.ZIP_FILE_SYSTEM_ACCESSOR, zipAccessor) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index e2e01dbefc1..46041002e64 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -105,6 +105,7 @@ class JsIrBackendContext( val devMode = configuration[JSConfigurationKeys.DEVELOPER_MODE] ?: false val errorPolicy = configuration[JSConfigurationKeys.ERROR_TOLERANCE_POLICY] ?: ErrorTolerancePolicy.DEFAULT override val es6mode = configuration[JSConfigurationKeys.USE_ES6_CLASSES] ?: false + val platformArgumentsProviderJsExpression = configuration[JSConfigurationKeys.DEFINE_PLATFORM_MAIN_FUNCTION_ARGUMENTS] val externalPackageFragment = mutableMapOf() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt index 02261d64262..6abe15edf19 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt @@ -179,7 +179,13 @@ internal class ICHasher { hashCalculator.update(value.ordinal) } - hashCalculator.updateConfigKeys(config, listOf(JSConfigurationKeys.SOURCE_MAP_PREFIX)) { value: String -> + hashCalculator.updateConfigKeys( + config, + listOf( + JSConfigurationKeys.SOURCE_MAP_PREFIX, + JSConfigurationKeys.DEFINE_PLATFORM_MAIN_FUNCTION_ARGUMENTS + ) + ) { value: String -> hashCalculator.update(value) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MainFunctionCallWrapperLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MainFunctionCallWrapperLowering.kt index d0c1504ac45..0c02a925ddb 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MainFunctionCallWrapperLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MainFunctionCallWrapperLowering.kt @@ -72,7 +72,11 @@ class MainFunctionCallWrapperLowering(private val context: JsIrBackendContext) : return listOfNotNull( runIf(generateArgv) { - JsIrBuilder.buildArray( + context.platformArgumentsProviderJsExpression?.let { + JsIrBuilder.buildCall(context.intrinsics.jsCode).apply { + putValueArgument(0, it.toIrConst(context.irBuiltIns.stringType)) + } + } ?: JsIrBuilder.buildArray( mainFunctionArgs.map { it.toIrConst(context.irBuiltIns.stringType) }, valueParameters.first().type, context.irBuiltIns.stringType diff --git a/compiler/testData/cli/js/jsExtraHelp.out b/compiler/testData/cli/js/jsExtraHelp.out index e30002e809f..9573e0c4f18 100644 --- a/compiler/testData/cli/js/jsExtraHelp.out +++ b/compiler/testData/cli/js/jsExtraHelp.out @@ -49,6 +49,8 @@ where advanced options include: Define the compile-time log level for partial linkage. -Xpartial-linkage={enable|disable} Use partial linkage mode. + -Xplatform-arguments-in-main-function + JS expression that will be executed in runtime and be put as an Array parameter of the main function -Xstrict-implicit-export-types Generate strict types for implicitly exported entities inside d.ts files. This is available in the IR backend only. -Xtyped-arrays Translate primitive arrays into JS typed arrays. -Xes-classes Let generated JavaScript code use ES2015 classes. diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java index cb9a8a4f2c3..abb5236cce8 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider; import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer; import org.jetbrains.kotlin.serialization.js.ModuleKind; +import javax.annotation.Nullable; import java.io.File; import java.util.List; import java.util.Map; @@ -80,6 +81,9 @@ public class JSConfigurationKeys { public static final CompilerConfigurationKey GENERATE_POLYFILLS = CompilerConfigurationKey.create("generate polyfills for newest properties, methods and classes from ES6+"); + public static final CompilerConfigurationKey DEFINE_PLATFORM_MAIN_FUNCTION_ARGUMENTS = + CompilerConfigurationKey.create("provide platform specific args as a parameter of the main function"); + public static final CompilerConfigurationKey GENERATE_DTS = CompilerConfigurationKey.create("generate TypeScript definition file"); diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt index d09144ae502..5b0b938b764 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt @@ -47,6 +47,24 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { } } + @DisplayName("nodejs main function arguments") + @GradleTest + fun testNodeJsMainArguments(gradleVersion: GradleVersion) { + project("kotlin-js-nodejs-project", gradleVersion) { + buildGradle.appendText( + """ + | + |tasks.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsExec).configureEach { + | args += ["test", "'Hello, World'"] + |} + """.trimMargin() + ) + build("nodeRun") { + assertOutputContains("ACCEPTED: test;'Hello, World'") + } + } + } + @DisplayName("stale output is cleaned") @GradleTest fun testCleanOutputWithEmptySources(gradleVersion: GradleVersion) { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-nodejs-project/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-nodejs-project/build.gradle index a5ca83678fd..5d7f9947f1a 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-nodejs-project/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-nodejs-project/build.gradle @@ -17,6 +17,7 @@ kotlin { useCommonJs() binaries.executable() nodejs { + passProcessArgvToMainFunction() } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-nodejs-project/src/main/kotlin/Base.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-nodejs-project/src/main/kotlin/Base.kt index e69d852c3e0..698d3d45182 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-nodejs-project/src/main/kotlin/Base.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-nodejs-project/src/main/kotlin/Base.kt @@ -11,4 +11,8 @@ fun best(): Int { fun simpleBest(): Int { return 73 +} + +fun main(args: Array) { + println("ACCEPTED: ${args.drop(2).joinToString(";")}") } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/ExperimentalMainFunctionArgumentsDsl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/ExperimentalMainFunctionArgumentsDsl.kt new file mode 100644 index 00000000000..1f4e26d2e14 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/ExperimentalMainFunctionArgumentsDsl.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.dsl + +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@Target(AnnotationTarget.FUNCTION) +annotation class ExperimentalMainFunctionArgumentsDsl diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinJsTargetDsl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinJsTargetDsl.kt index f80356a15cf..ad54188999d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinJsTargetDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/dsl/KotlinJsTargetDsl.kt @@ -47,6 +47,12 @@ interface KotlinJsTargetDsl : KotlinTarget, KotlinTargetWithNodeJsDsl { fun useCommonJs() fun useEsModules() + /** + * The function accepts [jsExpression] and puts this expression as the "args: Array" argument in place of main-function call + */ + @ExperimentalMainFunctionArgumentsDsl + fun passAsArgumentToMainFunction(jsExpression: String) + fun generateTypeScriptDefinitions() val binaries: KotlinJsBinaryContainer @@ -98,4 +104,7 @@ interface KotlinJsBrowserDsl : KotlinJsSubTargetDsl { interface KotlinJsNodeDsl : KotlinJsSubTargetDsl { fun runTask(body: Action) + + @ExperimentalMainFunctionArgumentsDsl + fun passProcessArgvToMainFunction() } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt index f22508db5a2..69c53f2f91c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinJsIrTarget.kt @@ -433,6 +433,19 @@ constructor( } + override fun passAsArgumentToMainFunction(jsExpression: String) { + compilations + .all { + it.binaries + .withType(JsIrBinary::class.java) + .all { + it.linkTask.configure { linkTask -> + linkTask.compilerOptions.freeCompilerArgs.add("-Xplatform-arguments-in-main-function=$jsExpression") + } + } + } + } + private fun KotlinJsOptions.configureCommonJsOptions() { moduleKind = "commonjs" sourceMap = true diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinNodeJsIr.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinNodeJsIr.kt index b28d12abbfb..fedf8c4a8dc 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinNodeJsIr.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinNodeJsIr.kt @@ -32,6 +32,10 @@ abstract class KotlinNodeJsIr @Inject constructor(target: KotlinJsIrTarget) : project.tasks.withType().named(runTaskName).configure(body) } + override fun passProcessArgvToMainFunction() { + target.passAsArgumentToMainFunction("process.argv") + } + override fun locateOrRegisterRunTask(binary: JsIrBinary, name: String) { if (project.locateTask(name) != null) return