diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinBrowserJsIr.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinBrowserJsIr.kt index 591b2f2a93f..da194056727 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinBrowserJsIr.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/ir/KotlinBrowserJsIr.kt @@ -11,9 +11,11 @@ import org.gradle.api.provider.Provider import org.gradle.api.tasks.Copy import org.gradle.language.base.plugins.LifecycleBasePlugin import org.jetbrains.kotlin.gradle.dsl.KotlinJsDce +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.report.BuildMetricsService +import org.jetbrains.kotlin.gradle.targets.js.addWasmExperimentalArguments import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDceDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBrowserDsl @@ -128,11 +130,30 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) : )() task.description = "start ${mode.name.toLowerCaseAsciiOnly()} webpack dev server" + val openValue = if (compilation.platformType == KotlinPlatformType.wasm) { + KotlinWebpackConfig.DevServer.App( + KotlinWebpackConfig.DevServer.App.Browser( + "chrome canary", + listOf( + "--js-flags=" + + mutableListOf() + .apply { addWasmExperimentalArguments() } + .joinToString(" ") + ) + ), + ) + } else { + true + } + webpackMajorVersion.choose( { task.devServer = KotlinWebpackConfig.DevServer( - open = true, - static = mutableListOf(compilation.output.resourcesDir.canonicalPath), + open = openValue, + static = mutableListOf( + "./kotlin", + compilation.output.resourcesDir.canonicalPath, + ), client = KotlinWebpackConfig.DevServer.Client( KotlinWebpackConfig.DevServer.Client.Overlay( errors = true, diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfig.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfig.kt index 9957324f335..fca84463a61 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfig.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfig.kt @@ -7,9 +7,10 @@ package org.jetbrains.kotlin.gradle.targets.js.webpack +import com.google.gson.Gson import com.google.gson.GsonBuilder -import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer -import org.gradle.api.model.ObjectFactory +import com.google.gson.JsonArray +import com.google.gson.JsonObject import org.gradle.api.provider.Provider import org.gradle.api.tasks.* import org.gradle.work.NormalizeLineEndings @@ -154,6 +155,15 @@ data class KotlinWebpackConfig( var warnings: Boolean ) : Serializable } + + data class App( + var browser: Browser + ) : Serializable { + data class Browser( + var name: String, + var args: List + ) : Serializable + } } fun save(configFile: File) { @@ -216,7 +226,40 @@ data class KotlinWebpackConfig( if (devServer == null) return appendLine("// dev server") - appendLine("config.devServer = ${json(devServer!!)};") + + if (devServer!!.open !is DevServer.App) { + appendLine("config.devServer = ${json(devServer!!)};") + } else { + val open = devServer!!.open as DevServer.App + + val jsonO = Gson().toJsonTree(devServer!!).asJsonObject + + jsonO.add( + "open", + JsonObject().apply { + add( + "app", + JsonObject().apply { + addProperty("name", "${"$"}browserName${"$"}") + add("arguments", Gson().toJsonTree(open.browser.args).asJsonArray) + } + ) + } + ) + + //language=ES6 + appendLine( + """ + // noinspection JSUnnecessarySemicolon + ;(function(config) { + const apps = require('kotlin-test-js-runner/detect-correct-browser'); + const browserName = apps[${open.browser.name.jsQuoted()}] + config.devServer = ${json(jsonO).replace("\"${"$"}browserName${"$"}\"", "browserName.bin")} + })(config); + """.trimIndent() + ) + } + appendLine() } diff --git a/libraries/tools/kotlin-test-js-runner/build.gradle.kts b/libraries/tools/kotlin-test-js-runner/build.gradle.kts index 4a366a37695..1ca401257ca 100644 --- a/libraries/tools/kotlin-test-js-runner/build.gradle.kts +++ b/libraries/tools/kotlin-test-js-runner/build.gradle.kts @@ -50,6 +50,7 @@ tasks { "tc-log-appender.js", "tc-log-error-webpack.js", "webpack-5-debug.js", + "detect-correct-browser.js", "package.json", "rollup.config.js", "tsconfig.json", @@ -76,6 +77,7 @@ tasks { val jar by tasks.creating(Jar::class) { dependsOn(tasks.named("yarnBuild")) from(projectDir.resolve("lib")) + from(projectDir.resolve("package.json")) } artifacts { diff --git a/libraries/tools/kotlin-test-js-runner/detect-correct-browser.js b/libraries/tools/kotlin-test-js-runner/detect-correct-browser.js new file mode 100644 index 00000000000..2867842bf7a --- /dev/null +++ b/libraries/tools/kotlin-test-js-runner/detect-correct-browser.js @@ -0,0 +1,97 @@ +const fs = require("fs"); +const path = require("path"); +const isWsl = require('is-wsl'); +const {platform, arch} = process; + +function detectArchBinary(binary) { + if (typeof binary === 'string' || Array.isArray(binary)) { + return binary; + } + + const {[arch]: archBinary} = binary; + + if (!archBinary) { + throw new Error(`${arch} is not supported`); + } + + return archBinary; +} + +// Return location of chrome.exe file for a given Chrome directory (available: "Chrome", "Chrome SxS"). +function getChromeExe(chromeDirName) { + // Only run these checks on win32 + if (process.platform !== 'win32') { + return null + } + let windowsChromeDirectory, i, prefix; + const suffix = '\\Google\\' + chromeDirName + '\\Application\\chrome.exe'; + const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)']]; + + for (i = 0; i < prefixes.length; i++) { + prefix = prefixes[i] + try { + windowsChromeDirectory = path.join(prefix, suffix) + fs.accessSync(windowsChromeDirectory) + return windowsChromeDirectory + } catch (e) { + } + } + + return windowsChromeDirectory +} + +function detectPlatformBinary({[platform]: platformBinary}, {wsl}) { + if (wsl && isWsl) { + return detectArchBinary(wsl); + } + + if (!platformBinary) { + throw new Error(`${platform} is not supported`); + } + + return detectArchBinary(platformBinary); +} + +const apps = {}; + +function define(browserName, platforms, wsl) { + Object.defineProperty(apps, browserName, { + configurable: true, + enumerable: true, + value: { + bin: detectPlatformBinary(platforms, wsl) + } + }); +} + +define( + 'chrome', + { + darwin: 'google chrome', + win32: getChromeExe("Chrome"), + linux: ['google-chrome', 'google-chrome-stable'] + }, + { + wsl: { + ia32: '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe', + x64: ['/mnt/c/Program Files/Google/Chrome/Application/chrome.exe', '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe'] + } + } +); + +define( + 'chrome canary', + { + darwin: 'google chrome canary', + win32: getChromeExe("Chrome SxS"), + linux: ['google-chrome-canary', 'google-chrome-unstable'] + }, + { + wsl: { + ia32: '/mnt/c/Program Files (x86)/Google/Chrome SxS/Application/chrome.exe', + x64: ['/mnt/c/Program Files/Google/Chrome SxS/Application/chrome.exe', '/mnt/c/Program Files (x86)/Google/Chrome SxS/Application/chrome.exe'] + } + } +); + +module.exports = apps \ No newline at end of file diff --git a/libraries/tools/kotlin-test-js-runner/package.json b/libraries/tools/kotlin-test-js-runner/package.json index 07beb162020..1fa5e86a922 100644 --- a/libraries/tools/kotlin-test-js-runner/package.json +++ b/libraries/tools/kotlin-test-js-runner/package.json @@ -1,5 +1,5 @@ { - "name": "@kotlin/js-tests-teamcity", + "name": "kotlin-test-js-runner", "version": "0.0.1", "description": "Simple Kotlin/JS tests runner with TeamCity reporter", "main": "lib/index.js", @@ -26,6 +26,7 @@ "rollup-plugin-terser": "^7.0.2", "typescript": "^3.7.2", "format-util": "^1.0.5", - "tslib": "^2.3.1" + "tslib": "^2.3.1", + "is-wsl": "^2.2.0" } } diff --git a/libraries/tools/kotlin-test-js-runner/rollup.config.js b/libraries/tools/kotlin-test-js-runner/rollup.config.js index 8c78c14f00c..ad1434e12f8 100644 --- a/libraries/tools/kotlin-test-js-runner/rollup.config.js +++ b/libraries/tools/kotlin-test-js-runner/rollup.config.js @@ -90,7 +90,14 @@ export default [ file: 'lib/mocha-kotlin-reporter.js', format: 'cjs' } - } + }, + { + input: './detect-correct-browser.js', + output: { + file: 'lib/detect-correct-browser.js', + format: 'esm' + } + }, ] function plugins() { diff --git a/libraries/tools/kotlin-test-js-runner/yarn.lock b/libraries/tools/kotlin-test-js-runner/yarn.lock index ade37c24b86..99cfe60c5b0 100644 --- a/libraries/tools/kotlin-test-js-runner/yarn.lock +++ b/libraries/tools/kotlin-test-js-runner/yarn.lock @@ -302,6 +302,11 @@ is-core-module@^2.8.1: dependencies: has "^1.0.3" +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" @@ -314,6 +319,13 @@ is-reference@^1.2.1: dependencies: "@types/estree" "*" +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + jest-worker@^26.2.1: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"