From 322a07cf11bbcf216324e657dc99c729bb4a4208 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 17 May 2022 19:05:05 +0000 Subject: [PATCH 1/5] Implement support for a wasm app host --- Directory.Build.props | 5 +- app-support.js | 451 ++++ eng/pipelines/common/platform-matrix.yml | 1486 +++++------ eng/testing/tests.wasm.targets | 24 +- eng/testing/xunit/xunit.targets | 4 +- src/libraries/Directory.Build.targets | 5 +- .../tests/System.Buffers.Tests.csproj | 2 +- src/mono/mono/component/mini-wasm-debugger.c | 2 +- ...rosoft.NET.Runtime.WebAssembly.Sdk.pkgproj | 6 + .../Sdk/Sdk.targets.in | 1 + .../WorkloadManifest.targets.in | 2 +- src/mono/sample/wasm/Directory.Build.props | 3 + src/mono/wasm/Makefile | 6 +- src/mono/wasm/build/WasmApp.InTree.targets | 12 + src/mono/wasm/build/WasmApp.props | 2 +- src/mono/wasm/build/WasmApp.targets | 29 +- .../BrowserDebugHost/DebugProxyHost.cs | 2 +- .../wasm/debugger/BrowserDebugHost/Program.cs | 1 - .../wasm/debugger/BrowserDebugHost/Startup.cs | 2 +- .../BrowserDebugProxy.csproj | 1 + .../debugger/BrowserDebugProxy/DebugStore.cs | 12 +- .../BrowserDebugProxy/DebuggerProxy.cs | 7 +- .../BrowserDebugProxy/DevToolsProxy.cs | 2 +- .../Firefox/FireforDebuggerProxy.cs | 7 +- .../Firefox/FirefoxMonoProxy.cs | 2 +- .../debugger/BrowserDebugProxy/MonoProxy.cs | 13 +- .../ProxyOptions.cs | 1 + .../DebuggerTestSuite.csproj | 2 +- src/mono/wasm/host/BrowserArguments.cs | 48 + src/mono/wasm/host/BrowserHost.cs | 187 ++ src/mono/wasm/host/CommonConfiguration.cs | 131 + src/mono/wasm/host/JSEngineArguments.cs | 42 + src/mono/wasm/host/JSEngineHost.cs | 119 + src/mono/wasm/host/JsonExtensions.cs | 30 + src/mono/wasm/host/Options.cs | 2170 +++++++++++++++++ src/mono/wasm/host/Program.cs | 60 + src/mono/wasm/host/RunArgumentsJson.cs | 34 + src/mono/wasm/host/RunConfiguration.cs | 81 + src/mono/wasm/host/RuntimeConfigJson.cs | 38 + src/mono/wasm/host/Utils.cs | 57 + src/mono/wasm/host/WasmAppHost.csproj | 25 + src/mono/wasm/host/WasmHost.cs | 26 + src/mono/wasm/host/WasmLogMessage.cs | 12 + .../wasm/host/WasmTestMessagesProcessor.cs | 53 + src/mono/wasm/host/WebServer.cs | 82 + src/mono/wasm/host/WebServerOptions.cs | 20 + src/mono/wasm/host/WebServerStartup.cs | 81 + src/mono/wasm/runtime/startup.ts | 2 +- .../templates/templates/browser/Program.cs | 9 - .../templates/browser/app-support.js | 243 ++ .../templates/browser/browser.0.csproj | 4 +- .../wasm/templates/templates/browser/main.js | 14 +- .../browser/runtimeconfig.template.json | 11 + .../templates/templates/console/Program.cs | 8 +- .../templates/console/app-support.cjs | 253 ++ .../templates/console/console.0.csproj | 7 +- .../wasm/templates/templates/console/main.cjs | 11 +- .../console/runtimeconfig.template.json | 16 + src/mono/wasm/test-main.js | 583 +++-- src/tasks/WasmAppBuilder/JsonExtensions.cs | 23 + src/tasks/WasmAppBuilder/WasmAppBuilder.cs | 109 +- .../WasmAppBuilder/WasmAppBuilder.csproj | 1 + .../Wasm.Build.Tests/BrowserRunner.cs | 126 + .../Wasm.Build.Tests/RunCommand.cs | 18 + .../Wasm.Build.Tests/Wasm.Build.Tests.csproj | 11 + .../Wasm.Build.Tests/WasmTemplateTests.cs | 153 +- 66 files changed, 5890 insertions(+), 1100 deletions(-) create mode 100644 app-support.js rename src/mono/wasm/debugger/{BrowserDebugHost => BrowserDebugProxy}/ProxyOptions.cs (94%) create mode 100644 src/mono/wasm/host/BrowserArguments.cs create mode 100644 src/mono/wasm/host/BrowserHost.cs create mode 100644 src/mono/wasm/host/CommonConfiguration.cs create mode 100644 src/mono/wasm/host/JSEngineArguments.cs create mode 100644 src/mono/wasm/host/JSEngineHost.cs create mode 100644 src/mono/wasm/host/JsonExtensions.cs create mode 100644 src/mono/wasm/host/Options.cs create mode 100644 src/mono/wasm/host/Program.cs create mode 100644 src/mono/wasm/host/RunArgumentsJson.cs create mode 100644 src/mono/wasm/host/RunConfiguration.cs create mode 100644 src/mono/wasm/host/RuntimeConfigJson.cs create mode 100644 src/mono/wasm/host/Utils.cs create mode 100644 src/mono/wasm/host/WasmAppHost.csproj create mode 100644 src/mono/wasm/host/WasmHost.cs create mode 100644 src/mono/wasm/host/WasmLogMessage.cs create mode 100644 src/mono/wasm/host/WasmTestMessagesProcessor.cs create mode 100644 src/mono/wasm/host/WebServer.cs create mode 100644 src/mono/wasm/host/WebServerOptions.cs create mode 100644 src/mono/wasm/host/WebServerStartup.cs create mode 100644 src/mono/wasm/templates/templates/browser/app-support.js create mode 100644 src/mono/wasm/templates/templates/browser/runtimeconfig.template.json create mode 100644 src/mono/wasm/templates/templates/console/app-support.cjs create mode 100644 src/mono/wasm/templates/templates/console/runtimeconfig.template.json create mode 100644 src/tasks/WasmAppBuilder/JsonExtensions.cs create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/BrowserRunner.cs create mode 100644 src/tests/BuildWasmApps/Wasm.Build.Tests/RunCommand.cs diff --git a/Directory.Build.props b/Directory.Build.props index acee2f3fe1828c..87b262cc8d5097 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,7 @@ <_hostOS Condition="$([MSBuild]::IsOSPlatform('SOLARIS'))">Solaris <_hostOS Condition="$([MSBuild]::IsOSPlatform('WINDOWS'))">windows $(_hostOS) - <_hostOS Condition="'$(TargetOS)' == 'Browser'">Browser + browser $(_hostOS) true true @@ -34,7 +34,7 @@ arm64 loongarch64 s390x - wasm + wasm x64 x64 $(TargetArchitecture) @@ -108,6 +108,7 @@ $([MSBuild]::NormalizePath('$(AndroidAppBuilderDir)', 'AndroidAppBuilder.dll')) $([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll')) $([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll')) + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmAppHost', 'wasm', '$(Configuration)')) $([MSBuild]::NormalizePath('$(WorkloadBuildTasksDir)', 'WorkloadBuildTasks.dll')) $([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll')) $([MSBuild]::NormalizePath('$(MonoTargetsTasksDir)', 'MonoTargetsTasks.dll')) diff --git a/app-support.js b/app-support.js new file mode 100644 index 00000000000000..e55232ec706949 --- /dev/null +++ b/app-support.js @@ -0,0 +1,451 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// -*- mode: js; js-indent-level: 4; -*- +// +"use strict"; + +//glue code to deal with the differences between chrome, ch, d8, jsc and sm. +const is_browser = typeof window != "undefined"; +const is_node = !is_browser && typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string'; +export const App = {}; + +if (is_node && process.versions.node.split(".")[0] < 14) { + throw new Error(`NodeJS at '${process.execPath}' has too low version '${process.versions.node}'`); +} + +// if the engine doesn't provide a console +if (typeof (console) === "undefined") { + globalThis.console = { + log: globalThis.print, + clear: function () { } + }; +} +const originalConsole = { + log: console.log, + error: console.error +}; + +let consoleWebSocket; +let processedArguments = null; +let is_debugging = false; +// FIXME: process args before all this +let forward_console = true; + +function processArguments(incomingArguments) { + console.log("Incoming arguments: " + incomingArguments.join(' ')); + let profilers = []; + let setenv = {}; + let runtime_args = []; + let enable_gc = true; + let diagnostic_tracing = false; + let working_dir = '/'; + while (incomingArguments && incomingArguments.length > 0) { + const currentArg = incomingArguments[0]; + if (currentArg.startsWith("--profile=")) { + const arg = currentArg.substring("--profile=".length); + profilers.push(arg); + } else if (currentArg.startsWith("--setenv=")) { + const arg = currentArg.substring("--setenv=".length); + const parts = arg.split('='); + if (parts.length != 2) + set_exit_code(1, "Error: malformed argument: '" + currentArg); + setenv[parts[0]] = parts[1]; + } else if (currentArg.startsWith("--runtime-arg=")) { + const arg = currentArg.substring("--runtime-arg=".length); + runtime_args.push(arg); + } else if (currentArg == "--disable-on-demand-gc") { + enable_gc = false; + } else if (currentArg == "--diagnostic_tracing") { + diagnostic_tracing = true; + } else if (currentArg.startsWith("--working-dir=")) { + const arg = currentArg.substring("--working-dir=".length); + working_dir = arg; + } else if (currentArg == "--debug") { + is_debugging = true; + } else if (currentArg == "--no-forward-console") { + forward_console = false; + } else { + break; + } + incomingArguments = incomingArguments.slice(1); + } + + // cheap way to let the testing infrastructure know we're running in a browser context (or not) + setenv["IsBrowserDomSupported"] = is_browser.toString().toLowerCase(); + setenv["IsNodeJS"] = is_node.toString().toLowerCase(); + + console.log("Application arguments: " + incomingArguments.join(' ')); + + return { + applicationArgs: incomingArguments, + profilers, + setenv, + runtime_args, + enable_gc, + diagnostic_tracing, + working_dir, + } +} + +function proxyConsoleMethod(prefix, func, asJson) { + return function () { + try { + const args = [...arguments]; + let payload = args[0]; + if (payload === undefined) payload = 'undefined'; + else if (payload === null) payload = 'null'; + else if (typeof payload === 'function') payload = payload.toString(); + else if (typeof payload !== 'string') { + try { + payload = JSON.stringify(payload); + } catch (e) { + payload = payload.toString(); + } + } + + if (payload.startsWith("STARTRESULTXML")) { + originalConsole.log('Sending RESULTXML') + func(payload); + } + else if (asJson) { + func(JSON.stringify({ + method: prefix, + payload: payload, + arguments: args + })); + } else { + func([prefix + payload, ...args.slice(1)]); + } + } catch (err) { + originalConsole.error(`proxyConsole failed: ${err}`) + } + }; +}; + +// this can't be function because of `arguments` scope +try { + if (is_node) { + processedArguments = processArguments(process.argv.slice(2)); + } else if (is_browser) { + // We expect to be run by tests/runtime/run.js which passes in the arguments using http parameters + const url = new URL(decodeURI(window.location)); + let urlArguments = [] + for (let param of url.searchParams) { + if (param[0] == "arg") { + urlArguments.push(param[1]); + } + } + processedArguments = processArguments(urlArguments); + } else if (typeof arguments !== "undefined") { + processedArguments = processArguments(Array.from(arguments)); + } else if (typeof scriptArgs !== "undefined") { + processedArguments = processArguments(Array.from(scriptArgs)); + } else if (typeof WScript !== "undefined" && WScript.Arguments) { + processedArguments = processArguments(Array.from(WScript.Arguments)); + } +} catch (e) { + console.error(e); +} + +if (is_node) { + const modulesToLoad = processedArguments.setenv["NPM_MODULES"]; + if (modulesToLoad) { + modulesToLoad.split(',').forEach(module => { + const { 0:moduleName, 1:globalAlias } = module.split(':'); + + let message = `Loading npm '${moduleName}'`; + let moduleExport = require(moduleName); + + if (globalAlias) { + message += ` and attaching to global as '${globalAlias}'`; + globalThis[globalAlias] = moduleExport; + } else if(moduleName == "node-fetch") { + message += ' and attaching to global'; + globalThis.fetch = moduleExport.default; + globalThis.Headers = moduleExport.Headers; + globalThis.Request = moduleExport.Request; + globalThis.Response = moduleExport.Response; + } else if(moduleName == "node-abort-controller") { + message += ' and attaching to global'; + globalThis.AbortController = moduleExport.AbortController; + } + + console.log(message); + }); + } +} + +if (forward_console) { + const methods = ["debug", "trace", "warn", "info", "error"]; + for (let m of methods) { + if (typeof (console[m]) !== "function") { + console[m] = proxyConsoleMethod(`console.${m}: `, console.log, false); + } + } + + if (is_browser) { + const consoleUrl = `${window.location.origin}/console`.replace('http://', 'ws://'); + + consoleWebSocket = new WebSocket(consoleUrl); + consoleWebSocket.onopen = function (event) { + originalConsole.log("browser: Console websocket connected."); + }; + consoleWebSocket.onerror = function (event) { + originalConsole.error(`websocket error: ${event}`); + }; + consoleWebSocket.onclose = function (event) { + originalConsole.error(`websocket closed: ${event}`); + }; + + const send = (msg) => { + if (consoleWebSocket.readyState === WebSocket.OPEN) { + consoleWebSocket.send(msg); + } + else { + originalConsole.log(msg); + } + } + + // redirect output early, so that when emscripten starts it's already redirected + for (let m of ["log", ...methods]) + console[m] = proxyConsoleMethod(`console.${m}`, send, true); + } +} + +function stringify_as_error_with_stack(err) { + if (!err) + return ""; + + // FIXME: + if (App && App.INTERNAL) + return App.INTERNAL.mono_wasm_stringify_as_error_with_stack(err); + + if (err.stack) + return err.stack; + + if (typeof err == "string") + return err; + + return JSON.stringify(err); +} + +if (typeof globalThis.crypto === 'undefined') { + // **NOTE** this is a simple insecure polyfill for testing purposes only + // /dev/random doesn't work on js shells, so define our own + // See library_fs.js:createDefaultDevices () + globalThis.crypto = { + getRandomValues: function (buffer) { + for (let i = 0; i < buffer.length; i++) + buffer[i] = (Math.random() * 256) | 0; + } + } +} + +if (typeof globalThis.performance === 'undefined') { + if (is_node) { + const { performance } = require("perf_hooks"); + globalThis.performance = performance; + } else { + // performance.now() is used by emscripten and doesn't work in JSC + globalThis.performance = { + now: function () { + return Date.now(); + } + } + } +} + +let toAbsoluteUrl = function(possiblyRelativeUrl) { return possiblyRelativeUrl; } +if (is_browser) { + const anchorTagForAbsoluteUrlConversions = document.createElement('a'); + toAbsoluteUrl = function toAbsoluteUrl(possiblyRelativeUrl) { + anchorTagForAbsoluteUrlConversions.href = possiblyRelativeUrl; + return anchorTagForAbsoluteUrlConversions.href; + } +} + +if (is_node) { + module.exports.App = App; + module.exports.is_browser = is_browser; + module.exports.is_node = is_node; + module.exports.set_exit_code = set_exit_code; +} + +// Must be after loading npm modules. +processedArguments.setenv["IsWebSocketSupported"] = ("WebSocket" in globalThis).toString().toLowerCase(); + +loadDotnet("./dotnet.js").then((createDotnetRuntime) => { + return createDotnetRuntime(({ MONO, INTERNAL, BINDING, Module }) => ({ + disableDotnet6Compatibility: true, + config: null, + configSrc: "./mono-config.json", + locateFile: (path, prefix) => { + return toAbsoluteUrl(prefix + path); + }, + onConfigLoaded: (config) => { + if (!Module.config) { + const err = new Error("Could not find ./mono-config.json. Cancelling run"); + set_exit_code(1); + throw err; + } + // Have to set env vars here to enable setting MONO_LOG_LEVEL etc. + for (let variable in processedArguments.setenv) { + config.environment_variables[variable] = processedArguments.setenv[variable]; + } + config.diagnostic_tracing = !!processedArguments.diagnostic_tracing; + if (is_debugging && config.debug_level == 0) + config.debug_level = -1; + }, + preRun: () => { + if (!processedArguments.enable_gc) { + INTERNAL.mono_wasm_enable_on_demand_gc(0); + } + }, + onDotnetReady: () => { + let wds = Module.FS.stat(processedArguments.working_dir); + if (wds === undefined || !Module.FS.isDir(wds.mode)) { + set_exit_code(1, `Could not find working directory ${processedArguments.working_dir}`); + return; + } + + Module.FS.chdir(processedArguments.working_dir); + + if (processedArguments.runtime_args.length > 0) + INTERNAL.mono_wasm_set_runtime_options(processedArguments.runtime_args); + + console.info("Initializing....."); + Object.assign(App, { MONO, INTERNAL, BINDING, Module, processedArguments }); + + try { + if (App.init) + { + let ret = App.init(); + Promise.resolve(ret).then(function (code) { set_exit_code(code ?? 0); }); + } + else + { + console.log("WASM ERROR: no App.init defined"); + set_exit_code(1, "WASM ERROR: no App.init defined"); + } + } catch (err) { + console.log(`WASM ERROR ${err}`); + if (is_browser && document.getElementById("out")) + document.getElementById("out").innerHTML = `error: ${err}`; + set_exit_code(1, err); + } + }, + onAbort: (error) => { + set_exit_code(1, stringify_as_error_with_stack(new Error())); + }, + })) +}).catch(function (err) { + set_exit_code(1, "failed to load the dotnet.js file.\n" + stringify_as_error_with_stack(err)); +}); + +function set_exit_code(exit_code, reason) { + if (reason) { + if (reason instanceof Error) + console.error(stringify_as_error_with_stack(reason)); + else if (typeof reason == "string") + console.error(reason); + else + console.error(JSON.stringify(reason)); + } + + if (is_browser) { + if (App.Module) { + // Notify the selenium script + App.Module.exit_code = exit_code; + } + + //Tell xharness WasmBrowserTestRunner what was the exit code + const tests_done_elem = document.createElement("label"); + tests_done_elem.id = "tests_done"; + tests_done_elem.innerHTML = exit_code.toString(); + document.body.appendChild(tests_done_elem); + + if (forward_console) { + const stop_when_ws_buffer_empty = () => { + if (consoleWebSocket.bufferedAmount == 0) { + // tell xharness WasmTestMessagesProcessor we are done. + // note this sends last few bytes into the same WS + console.log("WASM EXIT " + exit_code); + } + else { + setTimeout(stop_when_ws_buffer_empty, 100); + } + }; + stop_when_ws_buffer_empty(); + } else { + console.log("WASM EXIT " + exit_code); + } + + } else if (App && App.INTERNAL) { + App.INTERNAL.mono_wasm_exit(exit_code); + } +} + +let loadScript2 = undefined; +if (typeof WScript !== "undefined") { // Chakra + loadScript2 = function (file) { + return Promise.resolve(WScript.LoadScriptFile(file)); + }; +} else if (is_node) { // NodeJS + loadScript2 = function (file) { + return Promise.resolve(require(file)); + }; +} else if (is_browser) { // vanila JS in browser + loadScript2 = function (file) { + return Promise.resolve(import(file)); + } +} +else if (typeof globalThis.load !== 'undefined') { + loadScript2 = function (file) { + return Promise.resolve(globalThis.load(file)); + } +} +else { + throw new Error("Unknown environment, can't figure out a `loadScript2` to use"); +} +globalThis.loadScript2 = loadScript2; + +async function loadDotnet(file) { + let loadScript = undefined; + if (typeof WScript !== "undefined") { // Chakra + loadScript = function (file) { + WScript.LoadScriptFile(file); + return globalThis.createDotnetRuntime; + }; + } else if (is_node) { // NodeJS + loadScript = async function (file) { + return require(file); + }; + } else if (is_browser) { // vanila JS in browser + loadScript = function (file) { + var loaded = new Promise((resolve, reject) => { + globalThis.__onDotnetRuntimeLoaded = (createDotnetRuntime) => { + // this is callback we have in CJS version of the runtime + resolve(createDotnetRuntime); + }; + import(file).then(({ default: createDotnetRuntime }) => { + // this would work with ES6 default export + if (createDotnetRuntime) { + resolve(createDotnetRuntime); + } + }, reject); + }); + return loaded; + } + } + else if (typeof globalThis.load !== 'undefined') { + loadScript = async function (file) { + globalThis.load(file) + return globalThis.createDotnetRuntime; + } + } + else { + throw new Error("Unknown environment, can't load config"); + } + + return loadScript(file); +} diff --git a/eng/pipelines/common/platform-matrix.yml b/eng/pipelines/common/platform-matrix.yml index 02e2aff49d2403..2c17628e8a37d9 100644 --- a/eng/pipelines/common/platform-matrix.yml +++ b/eng/pipelines/common/platform-matrix.yml @@ -28,267 +28,267 @@ parameters: jobs: # Linux arm -- ${{ if or(containsValue(parameters.platforms, 'Linux_arm'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - archType: arm - targetRid: linux-arm - platform: Linux_arm - container: - image: ubuntu-18.04-cross-arm-20220426130400-6e40d49 - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - crossrootfsDir: '/crossrootfs/arm' - ${{ insert }}: ${{ parameters.jobParameters }} - -# Linux armv6 -- ${{ if containsValue(parameters.platforms, 'Linux_armv6') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - archType: armv6 - targetRid: linux-armv6 - platform: Linux_armv6 - container: - image: ubuntu-20.04-cross-armv6-raspbian-10-20211208135931-e6e3ac4 - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - crossrootfsDir: '/crossrootfs/armv6' - ${{ insert }}: ${{ parameters.jobParameters }} - -# Linux arm64 - -- ${{ if or(containsValue(parameters.platforms, 'Linux_arm64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - archType: arm64 - targetRid: linux-arm64 - platform: Linux_arm64 - container: - ${{ if eq(parameters.container, '') }}: - image: ubuntu-18.04-cross-arm64-20220427171722-6e40d49 - ${{ if ne(parameters.container, '') }}: - image: ${{ parameters.container }} - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - crossrootfsDir: '/crossrootfs/arm64' - ${{ insert }}: ${{ parameters.jobParameters }} - -# Linux musl x64 - -- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_x64'), eq(parameters.platformGroup, 'all')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - osSubgroup: _musl - archType: x64 - targetRid: linux-musl-x64 - platform: Linux_musl_x64 - container: - image: alpine-3.13-WithNode-20210910135845-c401c85 - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Linux musl arm - -- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_arm'), eq(parameters.platformGroup, 'all')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - osSubgroup: _musl - archType: arm - targetRid: linux-musl-arm - platform: Linux_musl_arm - container: - image: ubuntu-16.04-cross-arm-alpine-20210923140502-78f7860 - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - crossrootfsDir: '/crossrootfs/arm' - ${{ insert }}: ${{ parameters.jobParameters }} - -# Linux musl arm64 - -- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_arm64'), eq(parameters.platformGroup, 'all')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - osSubgroup: _musl - archType: arm64 - targetRid: linux-musl-arm64 - platform: Linux_musl_arm64 - container: - image: ubuntu-16.04-cross-arm64-alpine-20210923140502-78f7860 - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - crossrootfsDir: '/crossrootfs/arm64' - ${{ insert }}: ${{ parameters.jobParameters }} - -# Linux x64 - -- ${{ if or(containsValue(parameters.platforms, 'Linux_x64'), containsValue(parameters.platforms, 'CoreClrTestBuildHost'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - archType: x64 - targetRid: linux-x64 - platform: Linux_x64 - container: - ${{ if eq(parameters.container, '') }}: - image: centos-7-20210714125435-9b5bbc2 - ${{ if ne(parameters.container, '') }}: - image: ${{ parameters.container }} - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Linux x86 - -- ${{ if containsValue(parameters.platforms, 'Linux_x86') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - archType: x86 - targetRid: linux-x86 - platform: Linux_x86 - container: - image: ubuntu-18.04-cross-x86-linux-20211022152824-f853169 - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - crossrootfsDir: '/crossrootfs/x86' - disableClrTest: true - ${{ insert }}: ${{ parameters.jobParameters }} - -# Linux x64 Source Build - -- ${{ if containsValue(parameters.platforms, 'SourceBuild_Linux_x64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - archType: x64 - targetRid: linux-x64 - platform: Linux_x64 - container: - image: centos-7-source-build-20210714125450-5d87b80 - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - buildingOnSourceBuildImage: true - -# Linux s390x - -- ${{ if containsValue(parameters.platforms, 'Linux_s390x') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Linux - archType: s390x - targetRid: linux-s390x - platform: Linux_s390x - container: - image: ubuntu-18.04-cross-s390x-20201102145728-d6e0352 - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - crossrootfsDir: '/crossrootfs/s390x' - ${{ insert }}: ${{ parameters.jobParameters }} +#- ${{ if or(containsValue(parameters.platforms, 'Linux_arm'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #archType: arm + #targetRid: linux-arm + #platform: Linux_arm + #container: + #image: ubuntu-18.04-cross-arm-20220426130400-6e40d49 + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #crossrootfsDir: '/crossrootfs/arm' + #${{ insert }}: ${{ parameters.jobParameters }} + +## Linux armv6 +#- ${{ if containsValue(parameters.platforms, 'Linux_armv6') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #archType: armv6 + #targetRid: linux-armv6 + #platform: Linux_armv6 + #container: + #image: ubuntu-20.04-cross-armv6-raspbian-10-20211208135931-e6e3ac4 + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #crossrootfsDir: '/crossrootfs/armv6' + #${{ insert }}: ${{ parameters.jobParameters }} + +## Linux arm64 + +#- ${{ if or(containsValue(parameters.platforms, 'Linux_arm64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #archType: arm64 + #targetRid: linux-arm64 + #platform: Linux_arm64 + #container: + #${{ if eq(parameters.container, '') }}: + #image: ubuntu-18.04-cross-arm64-20220426130400-6e40d49 + #${{ if ne(parameters.container, '') }}: + #image: ${{ parameters.container }} + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #crossrootfsDir: '/crossrootfs/arm64' + #${{ insert }}: ${{ parameters.jobParameters }} + +## Linux musl x64 + +#- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_x64'), eq(parameters.platformGroup, 'all')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #osSubgroup: _musl + #archType: x64 + #targetRid: linux-musl-x64 + #platform: Linux_musl_x64 + #container: + #image: alpine-3.13-WithNode-20210910135845-c401c85 + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Linux musl arm + +#- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_arm'), eq(parameters.platformGroup, 'all')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #osSubgroup: _musl + #archType: arm + #targetRid: linux-musl-arm + #platform: Linux_musl_arm + #container: + #image: ubuntu-16.04-cross-arm-alpine-20210923140502-78f7860 + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #crossrootfsDir: '/crossrootfs/arm' + #${{ insert }}: ${{ parameters.jobParameters }} + +## Linux musl arm64 + +#- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_arm64'), eq(parameters.platformGroup, 'all')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #osSubgroup: _musl + #archType: arm64 + #targetRid: linux-musl-arm64 + #platform: Linux_musl_arm64 + #container: + #image: ubuntu-16.04-cross-arm64-alpine-20210923140502-78f7860 + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #crossrootfsDir: '/crossrootfs/arm64' + #${{ insert }}: ${{ parameters.jobParameters }} + +## Linux x64 + +#- ${{ if or(containsValue(parameters.platforms, 'Linux_x64'), containsValue(parameters.platforms, 'CoreClrTestBuildHost'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #archType: x64 + #targetRid: linux-x64 + #platform: Linux_x64 + #container: + #${{ if eq(parameters.container, '') }}: + #image: centos-7-20210714125435-9b5bbc2 + #${{ if ne(parameters.container, '') }}: + #image: ${{ parameters.container }} + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Linux x86 + +#- ${{ if containsValue(parameters.platforms, 'Linux_x86') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #archType: x86 + #targetRid: linux-x86 + #platform: Linux_x86 + #container: + #image: ubuntu-18.04-cross-x86-linux-20211022152824-f853169 + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #crossrootfsDir: '/crossrootfs/x86' + #disableClrTest: true + #${{ insert }}: ${{ parameters.jobParameters }} + +## Linux x64 Source Build + +#- ${{ if containsValue(parameters.platforms, 'SourceBuild_Linux_x64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #archType: x64 + #targetRid: linux-x64 + #platform: Linux_x64 + #container: + #image: centos-7-source-build-20210714125450-5d87b80 + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + #buildingOnSourceBuildImage: true + +## Linux s390x + +#- ${{ if containsValue(parameters.platforms, 'Linux_s390x') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Linux + #archType: s390x + #targetRid: linux-s390x + #platform: Linux_s390x + #container: + #image: ubuntu-18.04-cross-s390x-20201102145728-d6e0352 + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #crossrootfsDir: '/crossrootfs/s390x' + #${{ insert }}: ${{ parameters.jobParameters }} # WebAssembly @@ -360,485 +360,485 @@ jobs: ${{ insert }}: ${{ parameters.jobParameters }} # FreeBSD -- ${{ if containsValue(parameters.platforms, 'FreeBSD_x64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: FreeBSD - archType: x64 - targetRid: freebsd-x64 - platform: FreeBSD_x64 - container: - image: ubuntu-18.04-cross-freebsd-12-20210917001307-f13d79e - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - buildConfig: ${{ parameters.buildConfig }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - crossrootfsDir: '/crossrootfs/x64' - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Android x64 - -- ${{ if containsValue(parameters.platforms, 'Android_x64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Android - archType: x64 - targetRid: android-x64 - platform: Android_x64 - container: - image: ubuntu-18.04-android-20220131172314-3983b4e - registry: mcr - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Android x86 - -- ${{ if containsValue(parameters.platforms, 'Android_x86') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Android - archType: x86 - targetRid: android-x86 - platform: Android_x86 - container: - image: ubuntu-18.04-android-20220131172314-3983b4e - registry: mcr - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Android arm - -- ${{ if containsValue(parameters.platforms, 'Android_arm') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Android - archType: arm - targetRid: android-arm - platform: Android_arm - container: - image: ubuntu-18.04-android-20220131172314-3983b4e - registry: mcr - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Android arm64 - -- ${{ if containsValue(parameters.platforms, 'Android_arm64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Android - archType: arm64 - targetRid: android-arm64 - platform: Android_arm64 - container: - image: ubuntu-18.04-android-20220131172314-3983b4e - registry: mcr - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Mac Catalyst x64 - -- ${{ if containsValue(parameters.platforms, 'MacCatalyst_x64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: MacCatalyst - archType: x64 - targetRid: maccatalyst-x64 - platform: MacCatalyst_x64 - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Mac Catalyst arm64 - -- ${{ if containsValue(parameters.platforms, 'MacCatalyst_arm64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: MacCatalyst - archType: arm64 - targetRid: maccatalyst-arm64 - platform: MacCatalyst_arm64 - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# tvOS arm64 - -- ${{ if containsValue(parameters.platforms, 'tvOS_arm64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: tvOS - archType: arm64 - targetRid: tvos-arm64 - platform: tvOS_arm64 - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# tvOS Simulator x64 - -- ${{ if containsValue(parameters.platforms, 'tvOSSimulator_x64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: tvOSSimulator - archType: x64 - targetRid: tvossimulator-x64 - platform: tvOSSimulator_x64 - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# tvOS Simulator arm64 - -- ${{ if containsValue(parameters.platforms, 'tvOSSimulator_arm64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: tvOSSimulator - archType: arm64 - targetRid: tvossimulator-arm64 - platform: tvOSSimulator_arm64 - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# iOS arm - -- ${{ if containsValue(parameters.platforms, 'iOS_arm') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: iOS - archType: arm - targetRid: ios-arm - platform: iOS_arm - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# iOS arm64 - -- ${{ if containsValue(parameters.platforms, 'iOS_arm64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: iOS - archType: arm64 - targetRid: ios-arm64 - platform: iOS_arm64 - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# iOS Simulator x64 - -- ${{ if containsValue(parameters.platforms, 'iOSSimulator_x64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: iOSSimulator - archType: x64 - targetRid: iossimulator-x64 - platform: iOSSimulator_x64 - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# iOS Simulator x86 - -- ${{ if containsValue(parameters.platforms, 'iOSSimulator_x86') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: iOSSimulator - archType: x86 - targetRid: iossimulator-x86 - platform: iOSsimulator_x86 - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - managedTestBuildOsGroup: OSX - ${{ insert }}: ${{ parameters.jobParameters }} - -# iOS Simulator arm64 - -- ${{ if containsValue(parameters.platforms, 'iOSSimulator_arm64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: iOSSimulator - archType: arm64 - targetRid: iossimulator-arm64 - platform: iOSSimulator_arm64 - jobParameters: - runtimeFlavor: mono - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# macOS arm64 - -- ${{ if containsValue(parameters.platforms, 'OSX_arm64') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: OSX - archType: arm64 - targetRid: osx-arm64 - platform: OSX_arm64 - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - ${{ insert }}: ${{ parameters.jobParameters }} - -# macOS x64 - -- ${{ if or(containsValue(parameters.platforms, 'OSX_x64'), eq(parameters.platformGroup, 'all')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: OSX - archType: x64 - targetRid: osx-x64 - platform: OSX_x64 - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Tizen armel - -- ${{ if containsValue(parameters.platforms, 'Tizen_armel') }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: Tizen - archType: armel - targetRid: tizen-armel - platform: Tizen_armel - container: - image: ubuntu-18.04-cross-armel-tizen-20210719212651-8b02f56 - registry: mcr - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - crossBuild: true - crossrootfsDir: '/crossrootfs/armel' - disableClrTest: true - ${{ insert }}: ${{ parameters.jobParameters }} - -# Windows x64 - -- ${{ if or(containsValue(parameters.platforms, 'windows_x64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: windows - archType: x64 - targetRid: win-x64 - platform: windows_x64 - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Windows x86 - -- ${{ if or(containsValue(parameters.platforms, 'windows_x86'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: windows - archType: x86 - targetRid: win-x86 - platform: windows_x86 - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Windows arm -- ${{ if or(containsValue(parameters.platforms, 'windows_arm'), eq(parameters.platformGroup, 'all')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: windows - archType: arm - targetRid: win-arm - platform: windows_arm - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} - -# Windows arm64 - -- ${{ if or(containsValue(parameters.platforms, 'windows_arm64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - - template: xplat-setup.yml - parameters: - jobTemplate: ${{ parameters.jobTemplate }} - helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - variables: ${{ parameters.variables }} - osGroup: windows - archType: arm64 - targetRid: win-arm64 - platform: windows_arm64 - jobParameters: - runtimeFlavor: ${{ parameters.runtimeFlavor }} - stagedBuild: ${{ parameters.stagedBuild }} - buildConfig: ${{ parameters.buildConfig }} - ${{ if eq(parameters.passPlatforms, true) }}: - platforms: ${{ parameters.platforms }} - helixQueueGroup: ${{ parameters.helixQueueGroup }} - ${{ insert }}: ${{ parameters.jobParameters }} +#- ${{ if containsValue(parameters.platforms, 'FreeBSD_x64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: FreeBSD + #archType: x64 + #targetRid: freebsd-x64 + #platform: FreeBSD_x64 + #container: + #image: ubuntu-18.04-cross-freebsd-12-20210917001307-f13d79e + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #buildConfig: ${{ parameters.buildConfig }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #crossrootfsDir: '/crossrootfs/x64' + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Android x64 + +#- ${{ if containsValue(parameters.platforms, 'Android_x64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Android + #archType: x64 + #targetRid: android-x64 + #platform: Android_x64 + #container: + #image: ubuntu-18.04-android-20220131172314-3983b4e + #registry: mcr + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Android x86 + +#- ${{ if containsValue(parameters.platforms, 'Android_x86') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Android + #archType: x86 + #targetRid: android-x86 + #platform: Android_x86 + #container: + #image: ubuntu-18.04-android-20220131172314-3983b4e + #registry: mcr + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Android arm + +#- ${{ if containsValue(parameters.platforms, 'Android_arm') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Android + #archType: arm + #targetRid: android-arm + #platform: Android_arm + #container: + #image: ubuntu-18.04-android-20220131172314-3983b4e + #registry: mcr + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Android arm64 + +#- ${{ if containsValue(parameters.platforms, 'Android_arm64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Android + #archType: arm64 + #targetRid: android-arm64 + #platform: Android_arm64 + #container: + #image: ubuntu-18.04-android-20220131172314-3983b4e + #registry: mcr + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Mac Catalyst x64 + +#- ${{ if containsValue(parameters.platforms, 'MacCatalyst_x64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: MacCatalyst + #archType: x64 + #targetRid: maccatalyst-x64 + #platform: MacCatalyst_x64 + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Mac Catalyst arm64 + +#- ${{ if containsValue(parameters.platforms, 'MacCatalyst_arm64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: MacCatalyst + #archType: arm64 + #targetRid: maccatalyst-arm64 + #platform: MacCatalyst_arm64 + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## tvOS arm64 + +#- ${{ if containsValue(parameters.platforms, 'tvOS_arm64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: tvOS + #archType: arm64 + #targetRid: tvos-arm64 + #platform: tvOS_arm64 + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## tvOS Simulator x64 + +#- ${{ if containsValue(parameters.platforms, 'tvOSSimulator_x64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: tvOSSimulator + #archType: x64 + #targetRid: tvossimulator-x64 + #platform: tvOSSimulator_x64 + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## tvOS Simulator arm64 + +#- ${{ if containsValue(parameters.platforms, 'tvOSSimulator_arm64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: tvOSSimulator + #archType: arm64 + #targetRid: tvossimulator-arm64 + #platform: tvOSSimulator_arm64 + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## iOS arm + +#- ${{ if containsValue(parameters.platforms, 'iOS_arm') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: iOS + #archType: arm + #targetRid: ios-arm + #platform: iOS_arm + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## iOS arm64 + +#- ${{ if containsValue(parameters.platforms, 'iOS_arm64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: iOS + #archType: arm64 + #targetRid: ios-arm64 + #platform: iOS_arm64 + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## iOS Simulator x64 + +#- ${{ if containsValue(parameters.platforms, 'iOSSimulator_x64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: iOSSimulator + #archType: x64 + #targetRid: iossimulator-x64 + #platform: iOSSimulator_x64 + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## iOS Simulator x86 + +#- ${{ if containsValue(parameters.platforms, 'iOSSimulator_x86') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: iOSSimulator + #archType: x86 + #targetRid: iossimulator-x86 + #platform: iOSsimulator_x86 + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #managedTestBuildOsGroup: OSX + #${{ insert }}: ${{ parameters.jobParameters }} + +## iOS Simulator arm64 + +#- ${{ if containsValue(parameters.platforms, 'iOSSimulator_arm64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: iOSSimulator + #archType: arm64 + #targetRid: iossimulator-arm64 + #platform: iOSSimulator_arm64 + #jobParameters: + #runtimeFlavor: mono + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## macOS arm64 + +#- ${{ if containsValue(parameters.platforms, 'OSX_arm64') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: OSX + #archType: arm64 + #targetRid: osx-arm64 + #platform: OSX_arm64 + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #${{ insert }}: ${{ parameters.jobParameters }} + +## macOS x64 + +#- ${{ if or(containsValue(parameters.platforms, 'OSX_x64'), eq(parameters.platformGroup, 'all')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: OSX + #archType: x64 + #targetRid: osx-x64 + #platform: OSX_x64 + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Tizen armel + +#- ${{ if containsValue(parameters.platforms, 'Tizen_armel') }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: Tizen + #archType: armel + #targetRid: tizen-armel + #platform: Tizen_armel + #container: + #image: ubuntu-18.04-cross-armel-tizen-20210719212651-8b02f56 + #registry: mcr + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #crossBuild: true + #crossrootfsDir: '/crossrootfs/armel' + #disableClrTest: true + #${{ insert }}: ${{ parameters.jobParameters }} + +## Windows x64 + +#- ${{ if or(containsValue(parameters.platforms, 'windows_x64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: windows + #archType: x64 + #targetRid: win-x64 + #platform: windows_x64 + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Windows x86 + +#- ${{ if or(containsValue(parameters.platforms, 'windows_x86'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: windows + #archType: x86 + #targetRid: win-x86 + #platform: windows_x86 + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Windows arm +#- ${{ if or(containsValue(parameters.platforms, 'windows_arm'), eq(parameters.platformGroup, 'all')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: windows + #archType: arm + #targetRid: win-arm + #platform: windows_arm + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} + +## Windows arm64 + +#- ${{ if or(containsValue(parameters.platforms, 'windows_arm64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + #- template: xplat-setup.yml + #parameters: + #jobTemplate: ${{ parameters.jobTemplate }} + #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + #variables: ${{ parameters.variables }} + #osGroup: windows + #archType: arm64 + #targetRid: win-arm64 + #platform: windows_arm64 + #jobParameters: + #runtimeFlavor: ${{ parameters.runtimeFlavor }} + #stagedBuild: ${{ parameters.stagedBuild }} + #buildConfig: ${{ parameters.buildConfig }} + #${{ if eq(parameters.passPlatforms, true) }}: + #platforms: ${{ parameters.platforms }} + #helixQueueGroup: ${{ parameters.helixQueueGroup }} + #${{ insert }}: ${{ parameters.jobParameters }} diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index 0981950df467b3..f4a2c8beea26a1 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -55,15 +55,8 @@ - - <_XHarnessArgs Condition="'$(OS)' != 'Windows_NT'">wasm $XHARNESS_COMMAND --app=. --output-directory=$XHARNESS_OUT - <_XHarnessArgs Condition="'$(OS)' == 'Windows_NT'">wasm %XHARNESS_COMMAND% --app=. --output-directory=%XHARNESS_OUT% - - <_XHarnessArgs Condition="'$(IsFunctionalTest)' == 'true'" >$(_XHarnessArgs) --expected-exit-code=$(ExpectedExitCode) - <_XHarnessArgs Condition="'$(WasmXHarnessArgs)' != ''" >$(_XHarnessArgs) $(WasmXHarnessArgs) - <_XHarnessArgs >$(_XHarnessArgs) -s dotnet.js.symbols - <_XHarnessArgs Condition="'$(WasmXHarnessArgsCli)' != ''" >$(_XHarnessArgs) $(WasmXHarnessArgsCli) + <_AppArgs Condition="'$(IsFunctionalTest)' != 'true' and '$(Scenario)' != 'BuildWasmApps' and '$(WasmMainAssemblyFileName)' == ''">--run WasmTestRunner.dll $(AssemblyName).dll <_AppArgs Condition="'$(IsFunctionalTest)' != 'true' and '$(WasmMainAssemblyFileName)' != ''">--run $(WasmMainAssemblyFileName) <_AppArgs Condition="'$(IsFunctionalTest)' == 'true'">--run $(AssemblyName).dll @@ -71,6 +64,16 @@ <_AppArgs Condition="'$(WasmTestAppArgs)' != ''">$(_AppArgs) $(WasmTestAppArgs) $(WasmXHarnessMonoArgs) --setenv=XHARNESS_LOG_TEST_START=1 + + + + <_XHarnessArgs Condition="'$(OS)' != 'Windows_NT'">wasm $XHARNESS_COMMAND --app=. --output-directory=$XHARNESS_OUT + <_XHarnessArgs Condition="'$(OS)' == 'Windows_NT'">wasm %XHARNESS_COMMAND% --app=. --output-directory=%XHARNESS_OUT% + + <_XHarnessArgs Condition="'$(IsFunctionalTest)' == 'true'" >$(_XHarnessArgs) --expected-exit-code=$(ExpectedExitCode) + <_XHarnessArgs Condition="'$(WasmXHarnessArgs)' != ''" >$(_XHarnessArgs) $(WasmXHarnessArgs) + <_XHarnessArgs >$(_XHarnessArgs) -s dotnet.js.symbols + <_XHarnessArgs Condition="'$(WasmXHarnessArgsCli)' != ''" >$(_XHarnessArgs) $(WasmXHarnessArgsCli) $HARNESS_RUNNER $(_XHarnessArgs) %24XHARNESS_ARGS %24WasmXHarnessArgs -- $(WasmXHarnessMonoArgs) %24WasmXHarnessMonoArgs $(_AppArgs) %24WasmTestAppArgs @@ -102,6 +105,11 @@ WasmTriggerPublishApp $(BundleTestWasmAppDependsOn);_BundleAOTTestWasmAppForHelix + + $(WasmAppHostDir)/WasmAppHost + + --runtime-config $(BundleDir)/WasmTestRunner.runtimeconfig.json $(WasmHostArguments) $(StartArguments) $(WasmXHarnessMonoArgs) $(_AppArgs) diff --git a/eng/testing/xunit/xunit.targets b/eng/testing/xunit/xunit.targets index 8a950eb58ef383..6b048e6f6a9a4e 100644 --- a/eng/testing/xunit/xunit.targets +++ b/eng/testing/xunit/xunit.targets @@ -8,10 +8,10 @@ - $(OutDir) + $(OutDir) - + $(DotNetTool) test $(TargetPath) --settings $(OutDir).runsettings diff --git a/src/libraries/Directory.Build.targets b/src/libraries/Directory.Build.targets index 1f444f8ffa8753..090f6d35b6d6b1 100644 --- a/src/libraries/Directory.Build.targets +++ b/src/libraries/Directory.Build.targets @@ -12,10 +12,11 @@ + false $(NetCoreAppCurrent)-$(TargetOS)-$(Configuration)-$(TargetArchitecture) $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'native', '$(NetCoreAppCurrentBuildSettings)')) - $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'testhost', '$(NetCoreAppCurrentBuildSettings)')) - $([MSBuild]::NormalizeDirectory('$(NetCoreAppCurrentTestHostPath)', 'shared', '$(MicrosoftNetCoreAppFrameworkName)', '$(ProductVersion)')) + $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'testhost', '$(NetCoreAppCurrentBuildSettings)')) + $([MSBuild]::NormalizeDirectory('$(NetCoreAppCurrentTestHostPath)', 'shared', '$(MicrosoftNetCoreAppFrameworkName)', '$(ProductVersion)')) $([MSBuild]::NormalizeDirectory('$(NuGetPackageRoot)', 'netstandard.library.ref', '$(NETStandardLibraryRefVersion)', 'ref', 'netstandard2.1')) $(NoWarn);nullable diff --git a/src/libraries/System.Buffers/tests/System.Buffers.Tests.csproj b/src/libraries/System.Buffers/tests/System.Buffers.Tests.csproj index d226c7d706dfa5..10b79281bf52b6 100644 --- a/src/libraries/System.Buffers/tests/System.Buffers.Tests.csproj +++ b/src/libraries/System.Buffers/tests/System.Buffers.Tests.csproj @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/src/mono/mono/component/mini-wasm-debugger.c b/src/mono/mono/component/mini-wasm-debugger.c index 28b83912a9daa0..c7d0770313f437 100644 --- a/src/mono/mono/component/mini-wasm-debugger.c +++ b/src/mono/mono/component/mini-wasm-debugger.c @@ -136,7 +136,7 @@ mono_wasm_enable_debugging_internal (int debug_level) { log_level = debug_level; if (debug_level != 0) { - PRINT_DEBUG_MSG (1, "DEBUGGING ENABLED\n"); + wasm_debugger_log(1, "DEBUGGING ENABLED\n"); debugger_enabled = TRUE; } } diff --git a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Microsoft.NET.Runtime.WebAssembly.Sdk.pkgproj b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Microsoft.NET.Runtime.WebAssembly.Sdk.pkgproj index 3116e9955cfcd3..1a864b3fc1446f 100644 --- a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Microsoft.NET.Runtime.WebAssembly.Sdk.pkgproj +++ b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Microsoft.NET.Runtime.WebAssembly.Sdk.pkgproj @@ -8,6 +8,7 @@ + @@ -33,7 +34,12 @@ + + <_WasmAppHostFiles Include="$(WasmAppHostDir)\*" TargetPath="WasmAppHost" /> + + + diff --git a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/Sdk.targets.in b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/Sdk.targets.in index 0a6137cd8c4edd..a6a33a57d12920 100644 --- a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/Sdk.targets.in +++ b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Sdk/Sdk/Sdk.targets.in @@ -6,6 +6,7 @@ $(_TasksDir)WasmAppBuilder.dll $(_TasksDir)WasmBuildTasks.dll + $([MSBuild]::NormalizeDirectory($(MSBuildThisFileDirectory), '..', 'WasmAppHost')) diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in index a9ff9602881fca..692c889f2f7018 100644 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Manifest/WorkloadManifest.targets.in @@ -9,7 +9,7 @@ - true + true $(WasmNativeWorkload) diff --git a/src/mono/sample/wasm/Directory.Build.props b/src/mono/sample/wasm/Directory.Build.props index 6fc27dc90d9ae7..f3614831a8f486 100644 --- a/src/mono/sample/wasm/Directory.Build.props +++ b/src/mono/sample/wasm/Directory.Build.props @@ -3,6 +3,9 @@ Release Exe + browser + wasm + browser-wasm diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index 819c29ea04a018..d7d45547ad1252 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -44,7 +44,7 @@ provision-wasm: .stamp-wasm-install-and-select-$(EMSCRIPTEN_VERSION) @echo "----------------------------------------------------------" @echo "Installed emsdk into EMSDK_PATH=$(TOP)/src/mono/wasm/emsdk" -MONO_OBJ_DIR=$(OBJDIR)/mono/Browser.wasm.$(CONFIG) +MONO_OBJ_DIR=$(OBJDIR)/mono/browser.wasm.$(CONFIG) BUILDS_OBJ_DIR=$(MONO_OBJ_DIR)/wasm clean-emsdk: @@ -58,10 +58,10 @@ clean-emsdk: .PHONY: build build: - EMSDK_PATH=$(EMSDK_PATH) $(TOP)/build.sh mono+libs.pretest -os Browser -c $(CONFIG) /p:ContinueOnError=false /p:StopOnFirstFailure=true $(MSBUILD_ARGS) + EMSDK_PATH=$(EMSDK_PATH) $(TOP)/build.sh mono+libs.pretest -os Browser -c $(CONFIG) --binaryLog /p:ContinueOnError=false /p:StopOnFirstFailure=true $(MSBUILD_ARGS) build-all: - EMSDK_PATH=$(EMSDK_PATH) $(TOP)/build.sh mono+libs -os Browser -c $(CONFIG) /p:ContinueOnError=false /p:StopOnFirstFailure=true $(MSBUILD_ARGS) + EMSDK_PATH=$(EMSDK_PATH) $(TOP)/build.sh mono+libs -os Browser -c $(CONFIG) --binaryLog /p:ContinueOnError=false /p:StopOnFirstFailure=true $(MSBUILD_ARGS) runtime: EMSDK_PATH=$(EMSDK_PATH) $(TOP)/build.sh mono.runtime+mono.wasmruntime+libs.native+libs.pretest -os Browser -c $(CONFIG) /p:ContinueOnError=false /p:StopOnFirstFailure=true $(MSBUILD_ARGS) diff --git a/src/mono/wasm/build/WasmApp.InTree.targets b/src/mono/wasm/build/WasmApp.InTree.targets index b4f2af25b0afd6..c4d37a5a061e72 100644 --- a/src/mono/wasm/build/WasmApp.InTree.targets +++ b/src/mono/wasm/build/WasmApp.InTree.targets @@ -7,6 +7,18 @@ + + + + <_WasmMainJSFileName>$([System.IO.Path]::GetFileName('$(WasmMainJSPath)')) + + + + + + + + diff --git a/src/mono/wasm/build/WasmApp.props b/src/mono/wasm/build/WasmApp.props index 7fdde27c4affb5..88f479968888d5 100644 --- a/src/mono/wasm/build/WasmApp.props +++ b/src/mono/wasm/build/WasmApp.props @@ -1,7 +1,7 @@ wasm - Browser + browser browser-wasm true diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index bc598307276bfc..765cb70e36aaa0 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -101,6 +101,26 @@ true true + true + false + false + + + -1 + + + <_AppBundleDirForRunCommand Condition="Exists('$(WasmAppDir)/$(AssemblyName).runtimeconfig.json')">$(WasmAppDir) + <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == '' and Exists('$(OutputPath)/AppBundle')">$([MSBuild]::NormalizeDirectory($(OutputPath), 'AppBundle')) + <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == '' and Exists('$(OutputPath)/browser-wasm/AppBundle')">$([MSBuild]::NormalizeDirectory($(OutputPath), 'browser-wasm', 'AppBundle')) + <_AppBundleDirForRunCommand Condition="'$(_AppBundleDirForRunCommand)' == ''">OutputPath=$(OutputPath), OutDir=$(OutDir) + + + + $([MSBuild]::NormalizePath($(WasmAppHostDir), 'WasmAppHost')) + --runtime-config $(_AppBundleDirForRunCommand)/$(AssemblyName).runtimeconfig.json $(WasmHostArguments) + $(_AppBundleDirForRunCommand) @@ -201,8 +221,6 @@ - true - false $([MSBuild]::NormalizeDirectory($(OutputPath), 'AppBundle')) $(TargetFileName) @@ -291,7 +309,12 @@ + + diff --git a/src/mono/wasm/debugger/BrowserDebugHost/DebugProxyHost.cs b/src/mono/wasm/debugger/BrowserDebugHost/DebugProxyHost.cs index d5ac839592266a..be3095eeffc74a 100644 --- a/src/mono/wasm/debugger/BrowserDebugHost/DebugProxyHost.cs +++ b/src/mono/wasm/debugger/BrowserDebugHost/DebugProxyHost.cs @@ -39,7 +39,7 @@ public static Task RunFirefoxServerLoopAsync(ProxyOptions options, string[] args loggerFactory, loggerFactory.CreateLogger("FirefoxMonoProxy"), token, - autoSetBreakpointOnEntryPoint: options.AutoSetBreakpointOnEntryPoint); + options); public static async Task RunDevToolsProxyAsync(ProxyOptions options, string[] args, ILoggerFactory loggerFactory, CancellationToken token) { diff --git a/src/mono/wasm/debugger/BrowserDebugHost/Program.cs b/src/mono/wasm/debugger/BrowserDebugHost/Program.cs index de41d4405fedc7..9e304560c72ab0 100644 --- a/src/mono/wasm/debugger/BrowserDebugHost/Program.cs +++ b/src/mono/wasm/debugger/BrowserDebugHost/Program.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.IO; using System.Threading; using System.Threading.Tasks; diff --git a/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs b/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs index 87b9f29288f601..4717f77153fe7e 100644 --- a/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs +++ b/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs @@ -199,7 +199,7 @@ async Task ConnectProxy(HttpContext context) { var loggerFactory = context.RequestServices.GetService(); context.Request.Query.TryGetValue("urlSymbolServer", out StringValues urlSymbolServerList); - var proxy = new DebuggerProxy(loggerFactory, urlSymbolServerList.ToList(), runtimeId); + var proxy = new DebuggerProxy(loggerFactory, urlSymbolServerList.ToList(), runtimeId, options: options); System.Net.WebSockets.WebSocket ideSocket = await context.WebSockets.AcceptWebSocketAsync(); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/BrowserDebugProxy.csproj b/src/mono/wasm/debugger/BrowserDebugProxy/BrowserDebugProxy.csproj index 0c5d37b7afe9a9..dc945e6de1e551 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/BrowserDebugProxy.csproj +++ b/src/mono/wasm/debugger/BrowserDebugProxy/BrowserDebugProxy.csproj @@ -4,6 +4,7 @@ $(AspNetCoreAppCurrent) $(NoWarn),CA2007 true + true diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 25f47ca8da5e9b..5b0e309563f785 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -70,7 +70,7 @@ internal sealed class BreakpointRequest public bool IsResolved => Assembly != null; public List Locations { get; set; } = new List(); - public override string ToString() => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; + public override string ToString() => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}, Id: {Id}"; public object AsSetBreakpointByUrlResponse(IEnumerable jsloc) => new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation()).Concat(jsloc) }; @@ -1227,7 +1227,6 @@ internal sealed class DebugStore internal List assemblies = new List(); private readonly ILogger logger; private readonly MonoProxy monoProxy; - private MethodInfo _entryPoint; public DebugStore(MonoProxy monoProxy, ILogger logger) { @@ -1345,12 +1344,11 @@ public async IAsyncEnumerable Load(SessionId id, string[] loaded_fil public SourceFile GetFileById(SourceId id) => AllSources().SingleOrDefault(f => f.SourceId.Equals(id)); public AssemblyInfo GetAssemblyByName(string name) => assemblies.FirstOrDefault(a => a.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); - public MethodInfo FindEntryPoint() + public MethodInfo FindEntryPoint(string preferredEntrypointAssembly) { - if (_entryPoint is null) - _entryPoint = assemblies.Where(asm => asm.EntryPoint is not null).Select(asm => asm.EntryPoint).FirstOrDefault(); - - return _entryPoint; + AssemblyInfo foundAsm = assemblies.FirstOrDefault(asm => asm.EntryPoint?.Assembly.Name == preferredEntrypointAssembly); + foundAsm ??= assemblies.FirstOrDefault(asm => asm.EntryPoint is not null); + return foundAsm?.EntryPoint; } /* diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs index 5a1b4cba1dd9a9..c4e7c5ef0cc764 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs @@ -18,13 +18,10 @@ public class DebuggerProxy : DebuggerProxyBase { internal MonoProxy MonoProxy { get; } - public DebuggerProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList, int runtimeId = 0, string loggerId = "", bool autoSetBreakpointOnEntryPoint = false) + public DebuggerProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList, int runtimeId = 0, string loggerId = "", ProxyOptions options = null) { string suffix = loggerId.Length > 0 ? $"-{loggerId}" : string.Empty; - MonoProxy = new MonoProxy(loggerFactory.CreateLogger($"DevToolsProxy{suffix}"), urlSymbolServerList, runtimeId, loggerId) - { - AutoSetBreakpointOnEntryPoint = autoSetBreakpointOnEntryPoint - }; + MonoProxy = new MonoProxy(loggerFactory.CreateLogger($"DevToolsProxy{suffix}"), urlSymbolServerList, runtimeId, loggerId, options); } public Task Run(Uri browserUri, WebSocket ideSocket, CancellationTokenSource cts) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs index 5bd321bad3df3e..9157b9b241c80a 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsProxy.cs @@ -209,7 +209,7 @@ protected virtual Task SendResponseInternal(MessageId id, Result result, Cancell { JObject o = result.ToJObject(id); if (!result.IsOk) - logger.LogError($"sending error response for id: {id} -> {result}"); + logger.LogDebug($"sending error response for id: {id} -> {result}"); return Send(this.ide, o, token); } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FireforDebuggerProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FireforDebuggerProxy.cs index b31c6d5f298f55..6824ff38110936 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FireforDebuggerProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FireforDebuggerProxy.cs @@ -33,7 +33,7 @@ public static void StartListener(int proxyPort, ILogger logger, int browserPort } } - public static async Task RunServerLoopAsync(int browserPort, int proxyPort, ILoggerFactory loggerFactory, ILogger logger, CancellationToken token, bool autoSetBreakpointOnEntryPoint = false) + public static async Task RunServerLoopAsync(int browserPort, int proxyPort, ILoggerFactory loggerFactory, ILogger logger, CancellationToken token, ProxyOptions? options = null) { StartListener(proxyPort, logger, browserPort); while (!token.IsCancellationRequested) @@ -46,10 +46,7 @@ public static async Task RunServerLoopAsync(int browserPort, int proxyPort, ILog { int id = Interlocked.Increment(ref s_nextId); logger.LogInformation($"IDE connected to the proxy, id: {id}"); - var monoProxy = new FirefoxMonoProxy(loggerFactory.CreateLogger($"{nameof(FirefoxMonoProxy)}-{id}"), id.ToString()) - { - AutoSetBreakpointOnEntryPoint = autoSetBreakpointOnEntryPoint - }; + var monoProxy = new FirefoxMonoProxy(loggerFactory.CreateLogger($"{nameof(FirefoxMonoProxy)}-{id}"), id.ToString(), options); await monoProxy.RunForFirefox(ideClient: ideClient, browserPort, cts); } catch (Exception ex) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs index 4e7f704117f6a3..ba8cdf740ef92e 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs @@ -17,7 +17,7 @@ namespace Microsoft.WebAssembly.Diagnostics; internal sealed class FirefoxMonoProxy : MonoProxy { - public FirefoxMonoProxy(ILogger logger, string loggerId = null) : base(logger, null, loggerId: loggerId) + public FirefoxMonoProxy(ILogger logger, string loggerId = null, ProxyOptions options = null) : base(logger, null, loggerId: loggerId, options: options) { } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 860e706851db1c..c7ba8454716e83 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -29,12 +29,13 @@ internal class MonoProxy : DevToolsProxy // index of the runtime in a same JS page/process public int RuntimeId { get; private init; } public bool JustMyCode { get; private set; } - public bool AutoSetBreakpointOnEntryPoint { get; set; } + protected readonly ProxyOptions _options; - public MonoProxy(ILogger logger, IList urlSymbolServerList, int runtimeId = 0, string loggerId = "") : base(logger, loggerId) + public MonoProxy(ILogger logger, IList urlSymbolServerList, int runtimeId = 0, string loggerId = "", ProxyOptions options = null) : base(logger, loggerId) { this.urlSymbolServerList = urlSymbolServerList ?? new List(); RuntimeId = runtimeId; + _options = options; } internal ExecutionContext GetContext(SessionId sessionId) @@ -1440,9 +1441,9 @@ internal async Task LoadStore(SessionId sessionId, CancellationToken await OnSourceFileAdded(sessionId, source, context, token); } - if (AutoSetBreakpointOnEntryPoint) + if (_options?.AutoSetBreakpointOnEntryPoint == true) { - var entryPoint = context.store.FindEntryPoint(); + MethodInfo entryPoint = context.store.FindEntryPoint(_options!.EntrypointAssembly); if (entryPoint is not null) { var sourceFile = entryPoint.Assembly.Sources.Single(sf => sf.SourceId == entryPoint.SourceId); @@ -1453,7 +1454,7 @@ internal async Task LoadStore(SessionId sessionId, CancellationToken columnNumber = entryPoint.StartLocation.Column, url = sourceFile.Url })); - logger.LogDebug($"Adding bp req {request}"); + logger.LogInformation($"Adding bp req {request}"); context.BreakpointRequests[bpId] = request; request.TryResolve(sourceFile); if (request.TryResolve(sourceFile)) @@ -1461,7 +1462,7 @@ internal async Task LoadStore(SessionId sessionId, CancellationToken } else { - logger.LogDebug($"No entrypoint found, for setting automatic breakpoint"); + logger.LogWarning($"No entrypoint found for setting automatic breakpoint"); } } } diff --git a/src/mono/wasm/debugger/BrowserDebugHost/ProxyOptions.cs b/src/mono/wasm/debugger/BrowserDebugProxy/ProxyOptions.cs similarity index 94% rename from src/mono/wasm/debugger/BrowserDebugHost/ProxyOptions.cs rename to src/mono/wasm/debugger/BrowserDebugProxy/ProxyOptions.cs index 2b28206dbbb6c1..c853faf4821646 100644 --- a/src/mono/wasm/debugger/BrowserDebugHost/ProxyOptions.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/ProxyOptions.cs @@ -28,5 +28,6 @@ public int DevToolsDebugPort } public string? LogPath { get; set; } public bool AutoSetBreakpointOnEntryPoint { get; set; } + public string? EntrypointAssembly { get; set; } public bool RunningForBlazor { get; set; } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj index 1490a4726a7442..f062f910b81815 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestSuite.csproj @@ -3,8 +3,8 @@ $(AspNetCoreAppCurrent) true - false true + false chrome $(DefineConstants);RUN_IN_CHROME windows diff --git a/src/mono/wasm/host/BrowserArguments.cs b/src/mono/wasm/host/BrowserArguments.cs new file mode 100644 index 00000000000000..4789417de419ca --- /dev/null +++ b/src/mono/wasm/host/BrowserArguments.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System.Collections.Generic; +using System.Text.Json; +using Mono.Options; + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed class BrowserArguments +{ + public string? HTMLPath { get; private set; } + public bool? ForwardConsoleOutput { get; private set; } + public bool UseQueryStringToPassArguments { get; private set; } + public string[] AppArgs { get; init; } + public CommonConfiguration CommonConfig { get; init; } + + public BrowserArguments(CommonConfiguration commonConfig) + { + CommonConfig = commonConfig; + AppArgs = GetOptions().Parse(commonConfig.RemainingArgs).ToArray(); + + ParseJsonProperties(CommonConfig.HostConfig.Properties); + } + + private OptionSet GetOptions() => new OptionSet + { + { "forward-console", "Forward JS console output", v => ForwardConsoleOutput = true }, + { "use-query-string-for-args", "Use query string to pass arguments (Default: false)", v => UseQueryStringToPassArguments = true } + }; + + public void ParseJsonProperties(IDictionary? properties) + { + if (properties?.TryGetValue("html-path", out JsonElement htmlPathElement) == true) + HTMLPath = htmlPathElement.GetString(); + if (properties?.TryGetValue("forward-console", out JsonElement forwardConsoleElement) == true) + ForwardConsoleOutput = forwardConsoleElement.GetBoolean(); + if (properties?.TryGetValue("use-query-string-for-args", out JsonElement useQueryElement) == true) + UseQueryStringToPassArguments = useQueryElement.GetBoolean(); + } + + public void Validate() + { + CommonConfiguration.CheckPathOrInAppPath(CommonConfig.AppPath, HTMLPath, "html-path"); + } +} diff --git a/src/mono/wasm/host/BrowserHost.cs b/src/mono/wasm/host/BrowserHost.cs new file mode 100644 index 00000000000000..eff6abb230b2f3 --- /dev/null +++ b/src/mono/wasm/host/BrowserHost.cs @@ -0,0 +1,187 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.WebAssembly.Diagnostics; + +#nullable enable + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed class BrowserHost +{ + private readonly ILogger _logger; + private readonly BrowserArguments _args; + + public BrowserHost(BrowserArguments args, ILogger logger) + { + _logger = logger; + _args = args; + } + + public static async Task InvokeAsync(CommonConfiguration commonArgs, + ILoggerFactory loggerFactory, + ILogger logger, + CancellationToken token) + { + var args = new BrowserArguments(commonArgs); + args.Validate(); + var host = new BrowserHost(args, logger); + await host.RunAsync(loggerFactory, token); + + return 0; + } + + private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken token) + { + if (_args.CommonConfig.Debugging) + { + ProxyOptions options = _args.CommonConfig.ToProxyOptions(); + _ = Task.Run(() => DebugProxyHost.RunDebugProxyAsync(options, Array.Empty(), loggerFactory, token), token) + .ConfigureAwait(false); + } + + Dictionary envVars = new(); + if (_args.CommonConfig.HostProperties.EnvironmentVariables is not null) + { + foreach (KeyValuePair kvp in _args.CommonConfig.HostProperties.EnvironmentVariables) + envVars[kvp.Key] = kvp.Value; + } + + foreach (DictionaryEntry de in Environment.GetEnvironmentVariables()) + { + if (de.Key is not null && de.Value is not null) + envVars[(string)de.Key] = (string)de.Value; + } + + var runArgsJson = new RunArgumentsJson(applicationArguments: _args.AppArgs, + runtimeArguments: _args.CommonConfig.RuntimeArguments, + environmentVariables: envVars, + forwardConsole: _args.ForwardConsoleOutput ?? false, + debugging: _args.CommonConfig.Debugging); + runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json")); + + (ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath, + _args.ForwardConsoleOutput ?? false, + _args.CommonConfig.HostProperties.WebServerPort, + token); + + string[] fullUrls = BuildUrls(serverURLs, + _args.UseQueryStringToPassArguments ? _args.AppArgs : Array.Empty()); + Console.WriteLine(); + foreach (string url in fullUrls) + Console.WriteLine($"App url: {url}"); + + await host.WaitForShutdownAsync(token); + } + + private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, int port, CancellationToken token) + { + WasmTestMessagesProcessor? logProcessor = null; + if (forwardConsole) + { + logProcessor = new(_logger); + } + + WebServerOptions options = new + ( + OnConsoleConnected: forwardConsole + ? socket => RunConsoleMessagesPump(socket, logProcessor!, token) + : null, + ContentRootPath: Path.GetFullPath(appPath), + WebServerUseCors: true, + WebServerUseCrossOriginPolicy: true, + Port: port + ); + + (ServerURLs serverURLs, IWebHost host) = await WebServer.StartAsync(options, _logger, token); + return (serverURLs, host); + } + + private async Task RunConsoleMessagesPump(WebSocket socket, WasmTestMessagesProcessor messagesProcessor, CancellationToken token) + { + byte[] buff = new byte[4000]; + var mem = new MemoryStream(); + try + { + while (!token.IsCancellationRequested) + { + if (socket.State != WebSocketState.Open) + { + _logger.LogError($"Console websocket is no longer open"); + break; + } + + WebSocketReceiveResult result = await socket.ReceiveAsync(new ArraySegment(buff), token).ConfigureAwait(false); + if (result.MessageType == WebSocketMessageType.Close) + { + // tcs.SetResult(false); + return; + } + + mem.Write(buff, 0, result.Count); + + if (result.EndOfMessage) + { + string? line = Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length); + line += Environment.NewLine; + + messagesProcessor.Invoke(line); + mem.SetLength(0); + mem.Seek(0, SeekOrigin.Begin); + } + } + } + catch (OperationCanceledException oce) + { + if (!token.IsCancellationRequested) + _logger.LogDebug($"RunConsoleMessagesPump cancelled: {oce}"); + } + catch (Exception ex) + { + _logger.LogError($"Console pump failed: {ex}"); + throw; + } + } + + private string[] BuildUrls(ServerURLs serverURLs, IEnumerable passThroughArguments) + { + var sb = new StringBuilder(); + foreach (string arg in passThroughArguments) + { + if (sb.Length > 0) + sb.Append('&'); + + sb.Append($"arg={HttpUtility.UrlEncode(arg)}"); + } + + string query = sb.ToString(); + string filename = Path.GetFileName(_args.HTMLPath!); + string httpUrl = BuildUrl(serverURLs.Http, filename, query); + + return string.IsNullOrEmpty(serverURLs.Https) + ? (new[] { httpUrl }) + : (new[] + { + httpUrl, + BuildUrl(serverURLs.Https!, filename, query) + }); + + static string BuildUrl(string baseUrl, string htmlFileName, string query) + => new UriBuilder(baseUrl) + { + Query = query, + Path = htmlFileName + }.ToString(); + } +} diff --git a/src/mono/wasm/host/CommonConfiguration.cs b/src/mono/wasm/host/CommonConfiguration.cs new file mode 100644 index 00000000000000..427acff3dc34c8 --- /dev/null +++ b/src/mono/wasm/host/CommonConfiguration.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using Mono.Options; +using System.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using Microsoft.WebAssembly.Diagnostics; + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed class CommonConfiguration +{ + public bool Debugging { get; set; } + public string AppPath { get; init; } + public string[] RuntimeArguments => HostProperties.RuntimeArguments ?? Array.Empty(); + public IEnumerable RemainingArgs { get; init; } + public WasmHost Host { get; init; } + public HostConfig HostConfig { get; init; } + public WasmHostProperties HostProperties { get; init; } + + private string? hostArg; + private string? _runtimeConfigPath; + + public static CommonConfiguration FromCommandLineArguments(string[] args) => new CommonConfiguration(args); + + private CommonConfiguration(string[] args) + { + var options = new OptionSet + { + { "debug|d", "Start debug server", _ => Debugging = true }, + { "host|h=", "Host config name", v => hostArg = v }, + { "runtime-config|r=", "runtimeconfig.json path for the app", v => _runtimeConfigPath = v } + }; + + RemainingArgs = options.Parse(args); + if (string.IsNullOrEmpty(_runtimeConfigPath)) + { + string[] configs = Directory.EnumerateFiles(Environment.CurrentDirectory, "*.runtimeconfig.json").ToArray(); + if (configs.Length == 0) + throw new Exception($"Could not find any runtimeconfig.json in {Environment.CurrentDirectory}. Use --runtime-config= to specify the path"); + + if (configs.Length > 1) + throw new Exception($"Found multiple runtimeconfig.json files: {string.Join(", ", configs)}. Use --runtime-config= to specify one"); + + _runtimeConfigPath = Path.GetFullPath(configs[0]); + } + + AppPath = Path.GetDirectoryName(_runtimeConfigPath) ?? "."; + + if (string.IsNullOrEmpty(_runtimeConfigPath) || !File.Exists(_runtimeConfigPath)) + throw new Exception($"Cannot find runtime config at {_runtimeConfigPath}"); + + RuntimeConfig? rconfig = JsonSerializer.Deserialize( + File.ReadAllText(_runtimeConfigPath), + new JsonSerializerOptions(JsonSerializerDefaults.Web) + { + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + PropertyNameCaseInsensitive = true + }); + if (rconfig == null) + throw new Exception($"Failed to deserialize {_runtimeConfigPath}"); + + if (rconfig.RuntimeOptions == null) + throw new Exception($"Failed to deserialize {_runtimeConfigPath} - rconfig.RuntimeOptions"); + + HostProperties = rconfig.RuntimeOptions.WasmHostProperties; + if (HostProperties == null) + throw new Exception($"Failed to deserialize {_runtimeConfigPath} - config"); + + if (HostProperties.HostConfigs is null || HostProperties.HostConfigs.Count == 0) + throw new Exception($"no perHostConfigs found"); + + // read only if it wasn't overridden by command line option + string desiredConfig = hostArg ?? HostProperties.DefaultConfig; + HostConfig? foundConfig = HostProperties.HostConfigs + .Where(hc => string.Equals(hc.Name, desiredConfig, StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefault(); + + HostConfig = foundConfig ?? HostProperties.HostConfigs.First(); + if (HostConfig == null) + throw new Exception("no host config found"); + + // FIXME: validate hostconfig + if (!Enum.TryParse(HostConfig.HostString, ignoreCase: true, out WasmHost wasmHost)) + throw new Exception($"Unknown host {HostConfig.HostString} in config named {HostConfig.Name}"); + Host = wasmHost; + } + + public ProxyOptions ToProxyOptions() + { + ProxyOptions options = new(); + if (HostProperties.ChromeProxyPort is not null) + options.DevToolsProxyPort = HostProperties.ChromeProxyPort.Value; + if (HostProperties.ChromeDebuggingPort is not null) + options.DevToolsDebugPort = HostProperties.ChromeDebuggingPort.Value; + if (HostProperties.FirefoxProxyPort is not null) + options.FirefoxProxyPort = HostProperties.FirefoxProxyPort.Value; + if (HostProperties.FirefoxDebuggingPort is not null) + options.FirefoxDebugPort = HostProperties.FirefoxDebuggingPort.Value; + options.LogPath = "."; + options.AutoSetBreakpointOnEntryPoint = true; + if (!string.IsNullOrEmpty(HostProperties.MainAssembly)) + options.EntrypointAssembly = HostProperties.MainAssembly; + + return options; + } + + public static void CheckPathOrInAppPath(string appPath, string? path, string argName) + { + if (string.IsNullOrEmpty(path)) + throw new ArgumentNullException($"Missing value for {argName}"); + + if (Path.IsPathRooted(path)) + { + if (!File.Exists(path)) + throw new ArgumentException($"Cannot find {argName}: {path}"); + } + else + { + string fullPath = Path.Combine(appPath, path); + if (!File.Exists(fullPath)) + throw new ArgumentException($"Cannot find {argName} {path} in app directory {appPath}"); + } + } +} diff --git a/src/mono/wasm/host/JSEngineArguments.cs b/src/mono/wasm/host/JSEngineArguments.cs new file mode 100644 index 00000000000000..e7816f6d087276 --- /dev/null +++ b/src/mono/wasm/host/JSEngineArguments.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Text.Json; + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed class JSEngineArguments +{ + public string? JSPath { get; set; } + public WasmHost Host => CommonConfig.Host; + public CommonConfiguration CommonConfig { get; init; } + + // no js specific options + public IEnumerable AppArgs => CommonConfig.RemainingArgs; + + public JSEngineArguments(CommonConfiguration commonConfig) + { + CommonConfig = commonConfig; + + if (CommonConfig.Host is not (WasmHost.JavaScriptCore or WasmHost.NodeJS or WasmHost.SpiderMonkey or WasmHost.V8)) + throw new ArgumentException($"Internal error: host {CommonConfig.Host} not supported as a jsengine"); + + ParseJsonProperties(CommonConfig.HostConfig.Properties); + } + + private void ParseJsonProperties(IDictionary? properties) + { + if (properties?.TryGetValue("js-path", out JsonElement jsPathElement) == true && + jsPathElement.GetString() is string parsedPath) + JSPath = parsedPath; + } + + public void Validate() + { + CommonConfiguration.CheckPathOrInAppPath(CommonConfig.AppPath, JSPath, "js-path"); + } +} diff --git a/src/mono/wasm/host/JSEngineHost.cs b/src/mono/wasm/host/JSEngineHost.cs new file mode 100644 index 00000000000000..9ec8d70abc6de9 --- /dev/null +++ b/src/mono/wasm/host/JSEngineHost.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using System.Diagnostics; +using System.Threading; + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed class JSEngineHost +{ + private readonly JSEngineArguments _args; + private readonly ILogger _logger; + + public JSEngineHost(JSEngineArguments args, ILogger logger) + { + _args = args; + _logger = logger; + } + + public static async Task InvokeAsync(CommonConfiguration commonArgs, + ILoggerFactory loggerFactory, + ILogger logger, + CancellationToken token) + { + var args = new JSEngineArguments(commonArgs); + args.Validate(); + return await new JSEngineHost(args, logger).RunAsync(); + } + + private async Task RunAsync() + { + string[] engineArgs = Array.Empty(); + + string engineBinary = _args.Host switch + { + WasmHost.V8 => "v8", + WasmHost.JavaScriptCore => "jsc", + WasmHost.SpiderMonkey => "sm", + WasmHost.NodeJS => "node", + _ => throw new ArgumentException($"Unsupported engine {_args.Host}") + }; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (engineBinary.Equals("node")) + engineBinary = FindEngineInPath(engineBinary + ".exe"); // NodeJS ships as .exe rather than .cmd + else + engineBinary = FindEngineInPath(engineBinary + ".cmd"); + } + + if (_args.CommonConfig.Debugging) + throw new Exception($"Debugging not supported with {_args.Host}"); + + var args = new List(); + + if (_args.Host == WasmHost.V8) + { + // v8 needs this flag to enable WASM support + args.Add("--expose_wasm"); + } + + args.Add(_args.JSPath!); + + args.AddRange(engineArgs); + if (_args.Host is WasmHost.V8 or WasmHost.JavaScriptCore) + { + // v8/jsc want arguments to the script separated by "--", others don't + args.Add("--"); + } + foreach (var rarg in _args.CommonConfig.RuntimeArguments) + args.Add($"--runtime-arg={rarg}"); + + args.AddRange(_args.AppArgs); + + ProcessStartInfo psi = new() + { + FileName = engineBinary, + WorkingDirectory = _args.CommonConfig.AppPath + }; + + foreach (string? arg in args) + psi.ArgumentList.Add(arg!); + + int exitCode = await Utils.TryRunProcess(psi, + _logger, + msg => { if (msg != null) _logger.LogInformation(msg); }, + msg => { if (msg != null) _logger.LogInformation(msg); }); + + return exitCode; + } + + private static string FindEngineInPath(string engineBinary) + { + if (File.Exists(engineBinary) || Path.IsPathRooted(engineBinary)) + return engineBinary; + + var path = Environment.GetEnvironmentVariable("PATH"); + + if (path == null) + return engineBinary; + + foreach (var folder in path.Split(Path.PathSeparator)) + { + var fullPath = Path.Combine(folder, engineBinary); + if (File.Exists(fullPath)) + return fullPath; + } + + return engineBinary; + } +} diff --git a/src/mono/wasm/host/JsonExtensions.cs b/src/mono/wasm/host/JsonExtensions.cs new file mode 100644 index 00000000000000..0eeaa2e6b0c3ea --- /dev/null +++ b/src/mono/wasm/host/JsonExtensions.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Text.Json; + +namespace Microsoft.WebAssembly.AppHost; + +internal static class JsonExtensions +{ + public static bool TryGetPropertyByPath(this JsonElement element, string path, out JsonElement outElement) + { + outElement = default; + JsonElement cur = element; + string[] parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (string part in parts) + { + if (!cur.TryGetProperty(part, out JsonElement foundElement)) + return false; + + cur = foundElement; + } + + outElement = cur; + return true; + } +} diff --git a/src/mono/wasm/host/Options.cs b/src/mono/wasm/host/Options.cs new file mode 100644 index 00000000000000..3fa1de9912575b --- /dev/null +++ b/src/mono/wasm/host/Options.cs @@ -0,0 +1,2170 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// +// Options.cs +// +// Authors: +// Jonathan Pryor , +// Federico Di Gregorio +// Rolf Bjarne Kvinge +// +// Copyright (C) 2008 Novell (http://www.novell.com) +// Copyright (C) 2009 Federico Di Gregorio. +// Copyright (C) 2012 Xamarin Inc (http://www.xamarin.com) +// Copyright (C) 2017 Microsoft Corporation (http://www.microsoft.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +// Compile With: +// mcs -debug+ -r:System.Core Options.cs -o:Mono.Options.dll -t:library +// mcs -debug+ -d:LINQ -r:System.Core Options.cs -o:Mono.Options.dll -t:library +// +// The LINQ version just changes the implementation of +// OptionSet.Parse(IEnumerable), and confers no semantic changes. + +// +// A Getopt::Long-inspired option parsing library for C#. +// +// Mono.Options.OptionSet is built upon a key/value table, where the +// key is a option format string and the value is a delegate that is +// invoked when the format string is matched. +// +// Option format strings: +// Regex-like BNF Grammar: +// name: .+ +// type: [=:] +// sep: ( [^{}]+ | '{' .+ '}' )? +// aliases: ( name type sep ) ( '|' name type sep )* +// +// Each '|'-delimited name is an alias for the associated action. If the +// format string ends in a '=', it has a required value. If the format +// string ends in a ':', it has an optional value. If neither '=' or ':' +// is present, no value is supported. `=' or `:' need only be defined on one +// alias, but if they are provided on more than one they must be consistent. +// +// Each alias portion may also end with a "key/value separator", which is used +// to split option values if the option accepts > 1 value. If not specified, +// it defaults to '=' and ':'. If specified, it can be any character except +// '{' and '}' OR the *string* between '{' and '}'. If no separator should be +// used (i.e. the separate values should be distinct arguments), then "{}" +// should be used as the separator. +// +// Options are extracted either from the current option by looking for +// the option name followed by an '=' or ':', or is taken from the +// following option IFF: +// - The current option does not contain a '=' or a ':' +// - The current option requires a value (i.e. not a Option type of ':') +// +// The `name' used in the option format string does NOT include any leading +// option indicator, such as '-', '--', or '/'. All three of these are +// permitted/required on any named option. +// +// Option bundling is permitted so long as: +// - '-' is used to start the option group +// - all of the bundled options are a single character +// - at most one of the bundled options accepts a value, and the value +// provided starts from the next character to the end of the string. +// +// This allows specifying '-a -b -c' as '-abc', and specifying '-D name=value' +// as '-Dname=value'. +// +// Option processing is disabled by specifying "--". All options after "--" +// are returned by OptionSet.Parse() unchanged and unprocessed. +// +// Unprocessed options are returned from OptionSet.Parse(). +// +// Examples: +// int verbose = 0; +// OptionSet p = new OptionSet () +// .Add ("v", v => ++verbose) +// .Add ("name=|value=", v => Console.WriteLine (v)); +// p.Parse (new string[]{"-v", "--v", "/v", "-name=A", "/name", "B", "extra"}); +// +// The above would parse the argument string array, and would invoke the +// lambda expression three times, setting `verbose' to 3 when complete. +// It would also print out "A" and "B" to standard output. +// The returned array would contain the string "extra". +// +// C# 3.0 collection initializers are supported and encouraged: +// var p = new OptionSet () { +// { "h|?|help", v => ShowHelp () }, +// }; +// +// System.ComponentModel.TypeConverter is also supported, allowing the use of +// custom data types in the callback type; TypeConverter.ConvertFromString() +// is used to convert the value option to an instance of the specified +// type: +// +// var p = new OptionSet () { +// { "foo=", (Foo f) => Console.WriteLine (f.ToString ()) }, +// }; +// +// Random other tidbits: +// - Boolean options (those w/o '=' or ':' in the option format string) +// are explicitly enabled if they are followed with '+', and explicitly +// disabled if they are followed with '-': +// string a = null; +// var p = new OptionSet () { +// { "a", s => a = s }, +// }; +// p.Parse (new string[]{"-a"}); // sets v != null +// p.Parse (new string[]{"-a+"}); // sets v != null +// p.Parse (new string[]{"-a-"}); // sets v == null +// + +// +// Mono.Options.CommandSet allows easily having separate commands and +// associated command options, allowing creation of a *suite* along the +// lines of **git**(1), **svn**(1), etc. +// +// CommandSet allows intermixing plain text strings for `--help` output, +// Option values -- as supported by OptionSet -- and Command instances, +// which have a name, optional help text, and an optional OptionSet. +// +// var suite = new CommandSet ("suite-name") { +// // Use strings and option values, as with OptionSet +// "usage: suite-name COMMAND [OPTIONS]+", +// { "v:", "verbosity", (int? v) => Verbosity = v.HasValue ? v.Value : Verbosity+1 }, +// // Commands may also be specified +// new Command ("command-name", "command help") { +// Options = new OptionSet {/*...*/}, +// Run = args => { /*...*/}, +// }, +// new MyCommandSubclass (), +// }; +// return suite.Run (new string[]{...}); +// +// CommandSet provides a `help` command, and forwards `help COMMAND` +// to the registered Command instance by invoking Command.Invoke() +// with `--help` as an option. +// + +#nullable disable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.IO; +#if PCL +using System.Reflection; +#else +using System.Runtime.Serialization; +using System.Security.Permissions; +#endif +using System.Text; +using System.Text.RegularExpressions; + +#if LINQ +using System.Linq; +#endif + +#if TEST +using NDesk.Options; +#endif + +#if PCL +using MessageLocalizerConverter = System.Func; +#else +using MessageLocalizerConverter = System.Converter; +#endif + +#if NDESK_OPTIONS +namespace NDesk.Options +#else +namespace Mono.Options +#endif +{ + internal static class StringCoda + { + + public static IEnumerable WrappedLines(string self, params int[] widths) + { + IEnumerable w = widths; + return WrappedLines(self, w); + } + + public static IEnumerable WrappedLines(string self, IEnumerable widths) + { + if (widths == null) + throw new ArgumentNullException(nameof(widths)); + return CreateWrappedLinesIterator(self, widths); + } + + private static IEnumerable CreateWrappedLinesIterator(string self, IEnumerable widths) + { + if (string.IsNullOrEmpty(self)) + { + yield return string.Empty; + yield break; + } + using (IEnumerator ewidths = widths.GetEnumerator()) + { + bool? hw = null; + int width = GetNextWidth(ewidths, int.MaxValue, ref hw); + int start = 0, end; + do + { + end = GetLineEnd(start, width, self); + // endCorrection is 1 if the line end is '\n', and might be 2 if the line end is '\r\n'. + int endCorrection = 1; + if (end >= 2 && self.Substring(end - 2, 2).Equals("\r\n")) + endCorrection = 2; + char c = self[end - endCorrection]; + if (char.IsWhiteSpace(c)) + end -= endCorrection; + bool needContinuation = end != self.Length && !IsEolChar(c); + string continuation = ""; + if (needContinuation) + { + --end; + continuation = "-"; + } + string line = string.Concat(self.AsSpan(start, end - start), continuation); + yield return line; + start = end; + if (char.IsWhiteSpace(c)) + start += endCorrection; + width = GetNextWidth(ewidths, width, ref hw); + } while (start < self.Length); + } + } + + private static int GetNextWidth(IEnumerator ewidths, int curWidth, ref bool? eValid) + { + if (!eValid.HasValue || (eValid.HasValue && eValid.Value)) + { + curWidth = (eValid = ewidths.MoveNext()).Value ? ewidths.Current : curWidth; + // '.' is any character, - is for a continuation + const string minWidth = ".-"; + if (curWidth < minWidth.Length) + throw new ArgumentOutOfRangeException("widths", + string.Format("Element must be >= {0}, was {1}.", minWidth.Length, curWidth)); + return curWidth; + } + // no more elements, use the last element. + return curWidth; + } + + private static bool IsEolChar(char c) + { + return !char.IsLetterOrDigit(c); + } + + private static int GetLineEnd(int start, int length, string description) + { + int end = System.Math.Min(start + length, description.Length); + int sep = -1; + for (int i = start; i < end; ++i) + { + if (i + 2 <= description.Length && description.Substring(i, 2).Equals("\r\n")) + return i + 2; + if (description[i] == '\n') + return i + 1; + if (IsEolChar(description[i])) + sep = i + 1; + } + if (sep == -1 || end == description.Length) + return end; + return sep; + } + } + + public class OptionValueCollection : IList, IList + { + + private List values = new List(); + private OptionContext c; + + internal OptionValueCollection(OptionContext c) + { + this.c = c; + } + + #region ICollection + void ICollection.CopyTo(Array array, int index) { (values as ICollection).CopyTo(array, index); } + bool ICollection.IsSynchronized { get { return (values as ICollection).IsSynchronized; } } + object ICollection.SyncRoot { get { return (values as ICollection).SyncRoot; } } + #endregion + + #region ICollection + public void Add(string item) { values.Add(item); } + public void Clear() { values.Clear(); } + public bool Contains(string item) { return values.Contains(item); } + public void CopyTo(string[] array, int arrayIndex) { values.CopyTo(array, arrayIndex); } + public bool Remove(string item) { return values.Remove(item); } + public int Count { get { return values.Count; } } + public bool IsReadOnly { get { return false; } } + #endregion + + #region IEnumerable + IEnumerator IEnumerable.GetEnumerator() { return values.GetEnumerator(); } + #endregion + + #region IEnumerable + public IEnumerator GetEnumerator() { return values.GetEnumerator(); } + #endregion + + #region IList + int IList.Add(object value) { return (values as IList).Add(value); } + bool IList.Contains(object value) { return (values as IList).Contains(value); } + int IList.IndexOf(object value) { return (values as IList).IndexOf(value); } + void IList.Insert(int index, object value) { (values as IList).Insert(index, value); } + void IList.Remove(object value) { (values as IList).Remove(value); } + void IList.RemoveAt(int index) { (values as IList).RemoveAt(index); } + bool IList.IsFixedSize { get { return false; } } + object IList.this[int index] { get { return this[index]; } set { (values as IList)[index] = value; } } + #endregion + + #region IList + public int IndexOf(string item) { return values.IndexOf(item); } + public void Insert(int index, string item) { values.Insert(index, item); } + public void RemoveAt(int index) { values.RemoveAt(index); } + + private void AssertValid(int index) + { + if (c.Option == null) + throw new InvalidOperationException("OptionContext.Option is null."); + if (index >= c.Option.MaxValueCount) + throw new ArgumentOutOfRangeException(nameof(index)); + if (c.Option.OptionValueType == OptionValueType.Required && + index >= values.Count) + throw new OptionException(string.Format( + c.OptionSet.MessageLocalizer("Missing required value for option '{0}'."), c.OptionName), + c.OptionName); + } + + public string this[int index] + { + get + { + AssertValid(index); + return index >= values.Count ? null : values[index]; + } + set + { + values[index] = value; + } + } + #endregion + + public List ToList() + { + return new List(values); + } + + public string[] ToArray() + { + return values.ToArray(); + } + + public override string ToString() + { + return string.Join(", ", values.ToArray()); + } + } + + public class OptionContext + { + private Option option; + private string name; + private int index; + private OptionSet set; + private OptionValueCollection c; + + public OptionContext(OptionSet set) + { + this.set = set; + this.c = new OptionValueCollection(this); + } + + public Option Option + { + get { return option; } + set { option = value; } + } + + public string OptionName + { + get { return name; } + set { name = value; } + } + + public int OptionIndex + { + get { return index; } + set { index = value; } + } + + public OptionSet OptionSet + { + get { return set; } + } + + public OptionValueCollection OptionValues + { + get { return c; } + } + } + + public enum OptionValueType + { + None, + Optional, + Required, + } + + public abstract class Option + { + private string prototype, description; + private string[] names; + private OptionValueType type; + private int count; + private string[] separators; + private bool hidden; + + protected Option(string prototype, string description) + : this(prototype, description, 1, false) + { + } + + protected Option(string prototype, string description, int maxValueCount) + : this(prototype, description, maxValueCount, false) + { + } + + protected Option(string prototype, string description, int maxValueCount, bool hidden) + { + if (prototype == null) + throw new ArgumentNullException(nameof(prototype)); + if (prototype.Length == 0) + throw new ArgumentException("Cannot be the empty string.", nameof(prototype)); + if (maxValueCount < 0) + throw new ArgumentOutOfRangeException(nameof(maxValueCount)); + + this.prototype = prototype; + this.description = description; + this.count = maxValueCount; + this.names = (this is OptionSet.Category) + // append GetHashCode() so that "duplicate" categories have distinct + // names, e.g. adding multiple "" categories should be valid. + ? new[] { prototype + this.GetHashCode() } + : prototype.Split('|'); + + if (this is OptionSet.Category || this is CommandOption) + return; + + this.type = ParsePrototype(); + this.hidden = hidden; + + if (this.count == 0 && type != OptionValueType.None) + throw new ArgumentException( + "Cannot provide maxValueCount of 0 for OptionValueType.Required or " + + "OptionValueType.Optional.", + nameof(maxValueCount)); + if (this.type == OptionValueType.None && maxValueCount > 1) + throw new ArgumentException( + string.Format("Cannot provide maxValueCount of {0} for OptionValueType.None.", maxValueCount), + nameof(maxValueCount)); + if (Array.IndexOf(names, "<>") >= 0 && + ((names.Length == 1 && this.type != OptionValueType.None) || + (names.Length > 1 && this.MaxValueCount > 1))) + throw new ArgumentException( + "The default option handler '<>' cannot require values.", + nameof(prototype)); + } + + public string Prototype { get { return prototype; } } + public string Description { get { return description; } } + public OptionValueType OptionValueType { get { return type; } } + public int MaxValueCount { get { return count; } } + public bool Hidden { get { return hidden; } } + + public string[] GetNames() + { + return (string[])names.Clone(); + } + + public string[] GetValueSeparators() + { + if (separators == null) + return Array.Empty(); + return (string[])separators.Clone(); + } + + protected static T Parse(string value, OptionContext c) + { + Type tt = typeof(T); +#if PCL + TypeInfo ti = tt.GetTypeInfo (); +#else + Type ti = tt; +#endif + bool nullable = + ti.IsValueType && + ti.IsGenericType && + !ti.IsGenericTypeDefinition && + ti.GetGenericTypeDefinition() == typeof(Nullable<>); +#if PCL + Type targetType = nullable ? tt.GenericTypeArguments [0] : tt; +#else + Type targetType = nullable ? tt.GetGenericArguments()[0] : tt; +#endif + T t = default(T); + try + { + if (value != null) + { +#if PCL + if (targetType.GetTypeInfo ().IsEnum) + t = (T) Enum.Parse (targetType, value, true); + else + t = (T) Convert.ChangeType (value, targetType); +#else + TypeConverter conv = TypeDescriptor.GetConverter(targetType); + t = (T)conv.ConvertFromString(value); +#endif + } + } + catch (Exception e) + { + throw new OptionException( + string.Format( + c.OptionSet.MessageLocalizer("Could not convert string `{0}' to type {1} for option `{2}'."), + value, targetType.Name, c.OptionName), + c.OptionName, e); + } + return t; + } + + internal string[] Names { get { return names; } } + internal string[] ValueSeparators { get { return separators; } } + + private static readonly char[] NameTerminator = new char[] { '=', ':' }; + + private OptionValueType ParsePrototype() + { + char type = '\0'; + List seps = new List(); + for (int i = 0; i < names.Length; ++i) + { + string name = names[i]; + if (name.Length == 0) + throw new ArgumentException("Empty option names are not supported.", "prototype"); + + int end = name.IndexOfAny(NameTerminator); + if (end == -1) + continue; + names[i] = name.Substring(0, end); + if (type == '\0' || type == name[end]) + type = name[end]; + else + throw new ArgumentException( + string.Format("Conflicting option types: '{0}' vs. '{1}'.", type, name[end]), + "prototype"); + AddSeparators(name, end, seps); + } + + if (type == '\0') + return OptionValueType.None; + + if (count <= 1 && seps.Count != 0) + throw new ArgumentException( + string.Format("Cannot provide key/value separators for Options taking {0} value(s).", count), + "prototype"); + if (count > 1) + { + if (seps.Count == 0) + this.separators = new string[] { ":", "=" }; + else if (seps.Count == 1 && seps[0].Length == 0) + this.separators = null; + else + this.separators = seps.ToArray(); + } + + return type == '=' ? OptionValueType.Required : OptionValueType.Optional; + } + + private static void AddSeparators(string name, int end, ICollection seps) + { + int start = -1; + for (int i = end + 1; i < name.Length; ++i) + { + switch (name[i]) + { + case '{': + if (start != -1) + throw new ArgumentException( + string.Format("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + start = i + 1; + break; + case '}': + if (start == -1) + throw new ArgumentException( + string.Format("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + seps.Add(name.Substring(start, i - start)); + start = -1; + break; + default: + if (start == -1) + seps.Add(name[i].ToString()); + break; + } + } + if (start != -1) + throw new ArgumentException( + string.Format("Ill-formed name/value separator found in \"{0}\".", name), + "prototype"); + } + + public void Invoke(OptionContext c) + { + OnParseComplete(c); + c.OptionName = null; + c.Option = null; + c.OptionValues.Clear(); + } + + protected abstract void OnParseComplete(OptionContext c); + + internal void InvokeOnParseComplete(OptionContext c) + { + OnParseComplete(c); + } + + public override string ToString() + { + return Prototype; + } + } + + public abstract class ArgumentSource + { + + protected ArgumentSource() + { + } + + public abstract string[] GetNames(); + public abstract string Description { get; } + public abstract bool GetArguments(string value, out IEnumerable replacement); + +#if !PCL || NETSTANDARD1_3 + public static IEnumerable GetArgumentsFromFile(string file) + { + return GetArguments(File.OpenText(file), true); + } +#endif + + public static IEnumerable GetArguments(TextReader reader) + { + return GetArguments(reader, false); + } + + // Cribbed from mcs/driver.cs:LoadArgs(string) + private static IEnumerable GetArguments(TextReader reader, bool close) + { + try + { + StringBuilder arg = new StringBuilder(); + + string line; + while ((line = reader.ReadLine()) != null) + { + int t = line.Length; + + for (int i = 0; i < t; i++) + { + char c = line[i]; + + if (c == '"' || c == '\'') + { + char end = c; + + for (i++; i < t; i++) + { + c = line[i]; + + if (c == end) + break; + arg.Append(c); + } + } + else if (c == ' ') + { + if (arg.Length > 0) + { + yield return arg.ToString(); + arg.Length = 0; + } + } + else + arg.Append(c); + } + if (arg.Length > 0) + { + yield return arg.ToString(); + arg.Length = 0; + } + } + } + finally + { + if (close) + reader.Dispose(); + } + } + } + +#if !PCL || NETSTANDARD1_3 + public class ResponseFileSource : ArgumentSource + { + + public override string[] GetNames() + { + return new string[] { "@file" }; + } + + public override string Description + { + get { return "Read response file for more options."; } + } + + public override bool GetArguments(string value, out IEnumerable replacement) + { + if (string.IsNullOrEmpty(value) || !value.StartsWith("@")) + { + replacement = null; + return false; + } + replacement = ArgumentSource.GetArgumentsFromFile(value.Substring(1)); + return true; + } + } +#endif + +#if !PCL + [Serializable] +#endif + public class OptionException : Exception + { + private string option; + + public OptionException() + { + } + + public OptionException(string message, string optionName) + : base(message) + { + this.option = optionName; + } + + public OptionException(string message, string optionName, Exception innerException) + : base(message, innerException) + { + this.option = optionName; + } + +#if !PCL + protected OptionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + this.option = info.GetString("OptionName"); + } +#endif + + public string OptionName + { + get { return this.option; } + } + +#if !PCL +#pragma warning disable 618 // SecurityPermissionAttribute is obsolete + // [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] +#pragma warning restore 618 + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("OptionName", option); + } +#endif + } + + public delegate void OptionAction(TKey key, TValue value); + + public class OptionSet : KeyedCollection + { + public OptionSet() + : this(null, null) + { + } + + public OptionSet(MessageLocalizerConverter localizer) + : this(localizer, null) + { + } + + public OptionSet(StringComparer comparer) + : this(null, comparer) + { + } + + public OptionSet(MessageLocalizerConverter localizer, StringComparer comparer) + : base(comparer) + { + this.roSources = new ReadOnlyCollection(sources); + this.localizer = localizer; + if (this.localizer == null) + { + this.localizer = delegate (string f) + { + return f; + }; + } + } + + private MessageLocalizerConverter localizer; + + public MessageLocalizerConverter MessageLocalizer + { + get { return localizer; } + internal set { localizer = value; } + } + + private List sources = new List(); + private ReadOnlyCollection roSources; + + public ReadOnlyCollection ArgumentSources + { + get { return roSources; } + } + + + protected override string GetKeyForItem(Option item) + { + if (item == null) + throw new ArgumentNullException(nameof(item)); + if (item.Names != null && item.Names.Length > 0) + return item.Names[0]; + // This should never happen, as it's invalid for Option to be + // constructed w/o any names. + throw new InvalidOperationException("Option has no names!"); + } + + [Obsolete("Use KeyedCollection.this[string]")] + protected Option GetOptionForName(string option) + { + if (option == null) + throw new ArgumentNullException(nameof(option)); + try + { + return base[option]; + } + catch (KeyNotFoundException) + { + return null; + } + } + + protected override void InsertItem(int index, Option item) + { + base.InsertItem(index, item); + AddImpl(item); + } + + protected override void RemoveItem(int index) + { + Option p = Items[index]; + base.RemoveItem(index); + // KeyedCollection.RemoveItem() handles the 0th item + for (int i = 1; i < p.Names.Length; ++i) + { + Dictionary.Remove(p.Names[i]); + } + } + + protected override void SetItem(int index, Option item) + { + base.SetItem(index, item); + AddImpl(item); + } + + private void AddImpl(Option option) + { + if (option == null) + throw new ArgumentNullException(nameof(option)); + List added = new List(option.Names.Length); + try + { + // KeyedCollection.InsertItem/SetItem handle the 0th name. + for (int i = 1; i < option.Names.Length; ++i) + { + Dictionary.Add(option.Names[i], option); + added.Add(option.Names[i]); + } + } + catch (Exception) + { + foreach (string name in added) + Dictionary.Remove(name); + throw; + } + } + + public OptionSet Add(string header) + { + if (header == null) + throw new ArgumentNullException(nameof(header)); + Add(new Category(header)); + return this; + } + + internal sealed class Category : Option + { + + // Prototype starts with '=' because this is an invalid prototype + // (see Option.ParsePrototype(), and thus it'll prevent Category + // instances from being accidentally used as normal options. + public Category(string description) + : base("=:Category:= " + description, description) + { + } + + protected override void OnParseComplete(OptionContext c) + { + throw new NotSupportedException("Category.OnParseComplete should not be invoked."); + } + } + + + public new OptionSet Add(Option option) + { + base.Add(option); + return this; + } + + internal sealed class ActionOption : Option + { + private Action action; + + public ActionOption(string prototype, string description, int count, Action action) + : this(prototype, description, count, action, false) + { + } + + public ActionOption(string prototype, string description, int count, Action action, bool hidden) + : base(prototype, description, count, hidden) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + this.action = action; + } + + protected override void OnParseComplete(OptionContext c) + { + action(c.OptionValues); + } + } + + public OptionSet Add(string prototype, Action action) + { + return Add(prototype, null, action); + } + + public OptionSet Add(string prototype, string description, Action action) + { + return Add(prototype, description, action, false); + } + + public OptionSet Add(string prototype, string description, Action action, bool hidden) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + Option p = new ActionOption(prototype, description, 1, + delegate (OptionValueCollection v) { action(v[0]); }, hidden); + base.Add(p); + return this; + } + + public OptionSet Add(string prototype, OptionAction action) + { + return Add(prototype, null, action); + } + + public OptionSet Add(string prototype, string description, OptionAction action) + { + return Add(prototype, description, action, false); + } + + public OptionSet Add(string prototype, string description, OptionAction action, bool hidden) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + Option p = new ActionOption(prototype, description, 2, + delegate (OptionValueCollection v) { action(v[0], v[1]); }, hidden); + base.Add(p); + return this; + } + + internal sealed class ActionOption : Option + { + private Action action; + + public ActionOption(string prototype, string description, Action action) + : base(prototype, description, 1) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + this.action = action; + } + + protected override void OnParseComplete(OptionContext c) + { + action(Parse(c.OptionValues[0], c)); + } + } + + internal sealed class ActionOption : Option + { + private OptionAction action; + + public ActionOption(string prototype, string description, OptionAction action) + : base(prototype, description, 2) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + this.action = action; + } + + protected override void OnParseComplete(OptionContext c) + { + action( + Parse(c.OptionValues[0], c), + Parse(c.OptionValues[1], c)); + } + } + + public OptionSet Add(string prototype, Action action) + { + return Add(prototype, null, action); + } + + public OptionSet Add(string prototype, string description, Action action) + { + return Add(new ActionOption(prototype, description, action)); + } + + public OptionSet Add(string prototype, OptionAction action) + { + return Add(prototype, null, action); + } + + public OptionSet Add(string prototype, string description, OptionAction action) + { + return Add(new ActionOption(prototype, description, action)); + } + + public OptionSet Add(ArgumentSource source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + sources.Add(source); + return this; + } + + protected virtual OptionContext CreateOptionContext() + { + return new OptionContext(this); + } + + public List Parse(IEnumerable arguments) + { + if (arguments == null) + throw new ArgumentNullException(nameof(arguments)); + OptionContext c = CreateOptionContext(); + c.OptionIndex = -1; + bool process = true; + List unprocessed = new List(); + Option def = Contains("<>") ? this["<>"] : null; + ArgumentEnumerator ae = new ArgumentEnumerator(arguments); + foreach (string argument in ae) + { + ++c.OptionIndex; + if (argument == "--") + { + process = false; + continue; + } + if (!process) + { + Unprocessed(unprocessed, def, c, argument); + continue; + } + if (AddSource(ae, argument)) + continue; + if (!Parse(argument, c)) + Unprocessed(unprocessed, def, c, argument); + } + if (c.Option != null) + c.Option.Invoke(c); + return unprocessed; + } + + internal sealed class ArgumentEnumerator : IEnumerable + { + private List> sources = new List>(); + + public ArgumentEnumerator(IEnumerable arguments) + { + sources.Add(arguments.GetEnumerator()); + } + + public void Add(IEnumerable arguments) + { + sources.Add(arguments.GetEnumerator()); + } + + public IEnumerator GetEnumerator() + { + do + { + IEnumerator c = sources[sources.Count - 1]; + if (c.MoveNext()) + yield return c.Current; + else + { + c.Dispose(); + sources.RemoveAt(sources.Count - 1); + } + } while (sources.Count > 0); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + private bool AddSource(ArgumentEnumerator ae, string argument) + { + foreach (ArgumentSource source in sources) + { + IEnumerable replacement; + if (!source.GetArguments(argument, out replacement)) + continue; + ae.Add(replacement); + return true; + } + return false; + } + + private static bool Unprocessed(ICollection extra, Option def, OptionContext c, string argument) + { + if (def == null) + { + extra.Add(argument); + return false; + } + c.OptionValues.Add(argument); + c.Option = def; + c.Option.Invoke(c); + return false; + } + + private readonly Regex ValueOption = new Regex( + @"^(?--|-|/)(?[^:=]+)((?[:=])(?.*))?$"); + + protected bool GetOptionParts(string argument, out string flag, out string name, out string sep, out string value) + { + if (argument == null) + throw new ArgumentNullException(nameof(argument)); + + flag = name = sep = value = null; + Match m = ValueOption.Match(argument); + if (!m.Success) + { + return false; + } + flag = m.Groups["flag"].Value; + name = m.Groups["name"].Value; + if (m.Groups["sep"].Success && m.Groups["value"].Success) + { + sep = m.Groups["sep"].Value; + value = m.Groups["value"].Value; + } + return true; + } + + protected virtual bool Parse(string argument, OptionContext c) + { + if (c.Option != null) + { + ParseValue(argument, c); + return true; + } + + string f, n, s, v; + if (!GetOptionParts(argument, out f, out n, out s, out v)) + return false; + + Option p; + if (Contains(n)) + { + p = this[n]; + c.OptionName = f + n; + c.Option = p; + switch (p.OptionValueType) + { + case OptionValueType.None: + c.OptionValues.Add(n); + c.Option.Invoke(c); + break; + case OptionValueType.Optional: + case OptionValueType.Required: + ParseValue(v, c); + break; + } + return true; + } + // no match; is it a bool option? + if (ParseBool(argument, n, c)) + return true; + // is it a bundled option? + if (ParseBundledValue(f, string.Concat(n + s + v), c)) + return true; + + return false; + } + + private void ParseValue(string option, OptionContext c) + { + if (option != null) + foreach (string o in c.Option.ValueSeparators != null + ? option.Split(c.Option.ValueSeparators, c.Option.MaxValueCount - c.OptionValues.Count, StringSplitOptions.None) + : new string[] { option }) + { + c.OptionValues.Add(o); + } + if (c.OptionValues.Count == c.Option.MaxValueCount || + c.Option.OptionValueType == OptionValueType.Optional) + c.Option.Invoke(c); + else if (c.OptionValues.Count > c.Option.MaxValueCount) + { + throw new OptionException(localizer(string.Format( + "Error: Found {0} option values when expecting {1}.", + c.OptionValues.Count, c.Option.MaxValueCount)), + c.OptionName); + } + } + + private bool ParseBool(string option, string n, OptionContext c) + { + Option p; + string rn; + if (n.Length >= 1 && (n[n.Length - 1] == '+' || n[n.Length - 1] == '-') && + Contains((rn = n.Substring(0, n.Length - 1)))) + { + p = this[rn]; + string v = n[n.Length - 1] == '+' ? option : null; + c.OptionName = option; + c.Option = p; + c.OptionValues.Add(v); + p.Invoke(c); + return true; + } + return false; + } + + private bool ParseBundledValue(string f, string n, OptionContext c) + { + if (f != "-") + return false; + for (int i = 0; i < n.Length; ++i) + { + Option p; + string opt = f + n[i].ToString(); + string rn = n[i].ToString(); + if (!Contains(rn)) + { + if (i == 0) + return false; + throw new OptionException(string.Format(localizer( + "Cannot use unregistered option '{0}' in bundle '{1}'."), rn, f + n), null); + } + p = this[rn]; + switch (p.OptionValueType) + { + case OptionValueType.None: + Invoke(c, opt, n, p); + break; + case OptionValueType.Optional: + case OptionValueType.Required: + { + string v = n.Substring(i + 1); + c.Option = p; + c.OptionName = opt; + ParseValue(v.Length != 0 ? v : null, c); + return true; + } + default: + throw new InvalidOperationException("Unknown OptionValueType: " + p.OptionValueType); + } + } + return true; + } + + private static void Invoke(OptionContext c, string name, string value, Option option) + { + c.OptionName = name; + c.Option = option; + c.OptionValues.Add(value); + option.Invoke(c); + } + + private const int OptionWidth = 29; + private const int Description_FirstWidth = 80 - OptionWidth; + private const int Description_RemWidth = 80 - OptionWidth - 2; + + private static readonly string CommandHelpIndentStart = new string(' ', OptionWidth); + private static readonly string CommandHelpIndentRemaining = new string(' ', OptionWidth + 2); + + public void WriteOptionDescriptions(TextWriter o) + { + foreach (Option p in this) + { + int written = 0; + + if (p.Hidden) + continue; + + Category c = p as Category; + if (c != null) + { + WriteDescription(o, p.Description, "", 80, 80); + continue; + } + CommandOption co = p as CommandOption; + if (co != null) + { + WriteCommandDescription(o, co.Command, co.CommandName); + continue; + } + + if (!WriteOptionPrototype(o, p, ref written)) + continue; + + if (written < OptionWidth) + o.Write(new string(' ', OptionWidth - written)); + else + { + o.WriteLine(); + o.Write(new string(' ', OptionWidth)); + } + + WriteDescription(o, p.Description, new string(' ', OptionWidth + 2), + Description_FirstWidth, Description_RemWidth); + } + + foreach (ArgumentSource s in sources) + { + string[] names = s.GetNames(); + if (names == null || names.Length == 0) + continue; + + int written = 0; + + Write(o, ref written, " "); + Write(o, ref written, names[0]); + for (int i = 1; i < names.Length; ++i) + { + Write(o, ref written, ", "); + Write(o, ref written, names[i]); + } + + if (written < OptionWidth) + o.Write(new string(' ', OptionWidth - written)); + else + { + o.WriteLine(); + o.Write(new string(' ', OptionWidth)); + } + + WriteDescription(o, s.Description, new string(' ', OptionWidth + 2), + Description_FirstWidth, Description_RemWidth); + } + } + + internal void WriteCommandDescription(TextWriter o, Command c, string commandName) + { + var name = new string(' ', 8) + (commandName ?? c.Name); + if (name.Length < OptionWidth - 1) + { + WriteDescription(o, name + new string(' ', OptionWidth - name.Length) + c.Help, CommandHelpIndentRemaining, 80, Description_RemWidth); + } + else + { + WriteDescription(o, name, "", 80, 80); + WriteDescription(o, CommandHelpIndentStart + c.Help, CommandHelpIndentRemaining, 80, Description_RemWidth); + } + } + + private void WriteDescription(TextWriter o, string value, string prefix, int firstWidth, int remWidth) + { + bool indent = false; + foreach (string line in GetLines(localizer(GetDescription(value)), firstWidth, remWidth)) + { + if (indent) + o.Write(prefix); + o.WriteLine(line); + indent = true; + } + } + + private bool WriteOptionPrototype(TextWriter o, Option p, ref int written) + { + string[] names = p.Names; + + int i = GetNextOptionIndex(names, 0); + if (i == names.Length) + return false; + + if (names[i].Length == 1) + { + Write(o, ref written, " -"); + Write(o, ref written, names[0]); + } + else + { + Write(o, ref written, " --"); + Write(o, ref written, names[0]); + } + + for (i = GetNextOptionIndex(names, i + 1); + i < names.Length; i = GetNextOptionIndex(names, i + 1)) + { + Write(o, ref written, ", "); + Write(o, ref written, names[i].Length == 1 ? "-" : "--"); + Write(o, ref written, names[i]); + } + + if (p.OptionValueType == OptionValueType.Optional || + p.OptionValueType == OptionValueType.Required) + { + if (p.OptionValueType == OptionValueType.Optional) + { + Write(o, ref written, localizer("[")); + } + Write(o, ref written, localizer("=" + GetArgumentName(0, p.MaxValueCount, p.Description))); + string sep = p.ValueSeparators != null && p.ValueSeparators.Length > 0 + ? p.ValueSeparators[0] + : " "; + for (int c = 1; c < p.MaxValueCount; ++c) + { + Write(o, ref written, localizer(sep + GetArgumentName(c, p.MaxValueCount, p.Description))); + } + if (p.OptionValueType == OptionValueType.Optional) + { + Write(o, ref written, localizer("]")); + } + } + return true; + } + + private static int GetNextOptionIndex(string[] names, int i) + { + while (i < names.Length && names[i] == "<>") + { + ++i; + } + return i; + } + + private static void Write(TextWriter o, ref int n, string s) + { + n += s.Length; + o.Write(s); + } + + private static string GetArgumentName(int index, int maxIndex, string description) + { + var matches = Regex.Matches(description ?? "", @"(?<=(? 1 + if (maxIndex > 1 && parts.Length == 2 && + parts[0] == index.ToString(CultureInfo.InvariantCulture)) + { + argName = parts[1]; + } + } + + if (string.IsNullOrEmpty(argName)) + { + argName = maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1); + } + return argName; + } + + private static string GetDescription(string description) + { + if (description == null) + return string.Empty; + StringBuilder sb = new StringBuilder(description.Length); + int start = -1; + for (int i = 0; i < description.Length; ++i) + { + switch (description[i]) + { + case '{': + if (i == start) + { + sb.Append('{'); + start = -1; + } + else if (start < 0) + start = i + 1; + break; + case '}': + if (start < 0) + { + if ((i + 1) == description.Length || description[i + 1] != '}') + throw new InvalidOperationException("Invalid option description: " + description); + ++i; + sb.Append('}'); + } + else + { + sb.Append(description.AsSpan(start, i - start)); + start = -1; + } + break; + case ':': + if (start < 0) + goto default; + start = i + 1; + break; + default: + if (start < 0) + sb.Append(description[i]); + break; + } + } + return sb.ToString(); + } + + private static IEnumerable GetLines(string description, int firstWidth, int remWidth) + { + return StringCoda.WrappedLines(description, firstWidth, remWidth); + } + } + + public class Command + { + public string Name { get; } + public string Help { get; } + + public OptionSet Options { get; set; } + public Action> Run { get; set; } + + public CommandSet CommandSet { get; internal set; } + + public Command(string name, string help = null) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException(nameof(name)); + + Name = NormalizeCommandName(name); + Help = help; + } + + private static string NormalizeCommandName(string name) + { + var value = new StringBuilder(name.Length); + var space = false; + for (int i = 0; i < name.Length; ++i) + { + if (!char.IsWhiteSpace(name, i)) + { + space = false; + value.Append(name[i]); + } + else if (!space) + { + space = true; + value.Append(' '); + } + } + return value.ToString(); + } + + public virtual int Invoke(IEnumerable arguments) + { + var rest = Options?.Parse(arguments) ?? arguments; + Run?.Invoke(rest); + return 0; + } + } + + internal sealed class CommandOption : Option + { + public Command Command { get; } + public string CommandName { get; } + + // Prototype starts with '=' because this is an invalid prototype + // (see Option.ParsePrototype(), and thus it'll prevent Category + // instances from being accidentally used as normal options. + public CommandOption(Command command, string commandName = null, bool hidden = false) + : base("=:Command:= " + (commandName ?? command?.Name), (commandName ?? command?.Name), maxValueCount: 0, hidden: hidden) + { + if (command == null) + throw new ArgumentNullException(nameof(command)); + Command = command; + CommandName = commandName ?? command.Name; + } + + protected override void OnParseComplete(OptionContext c) + { + throw new NotSupportedException("CommandOption.OnParseComplete should not be invoked."); + } + } + + internal sealed class HelpOption : Option + { + private Option option; + private CommandSet commands; + + public HelpOption(CommandSet commands, Option d) + : base(d.Prototype, d.Description, d.MaxValueCount, d.Hidden) + { + this.commands = commands; + this.option = d; + } + + protected override void OnParseComplete(OptionContext c) + { + commands.showHelp = true; + + option?.InvokeOnParseComplete(c); + } + } + + internal sealed class CommandOptionSet : OptionSet + { + private CommandSet commands; + + public CommandOptionSet(CommandSet commands, MessageLocalizerConverter localizer) + : base(localizer) + { + this.commands = commands; + } + + protected override void SetItem(int index, Option item) + { + if (ShouldWrapOption(item)) + { + base.SetItem(index, new HelpOption(commands, item)); + return; + } + base.SetItem(index, item); + } + + private static bool ShouldWrapOption(Option item) + { + if (item == null) + return false; + var help = item as HelpOption; + if (help != null) + return false; + foreach (var n in item.Names) + { + if (n == "help") + return true; + } + return false; + } + + protected override void InsertItem(int index, Option item) + { + if (ShouldWrapOption(item)) + { + base.InsertItem(index, new HelpOption(commands, item)); + return; + } + base.InsertItem(index, item); + } + } + + public class CommandSet : KeyedCollection + { + private readonly string suite; + + private OptionSet options; + private TextWriter outWriter; + private TextWriter errorWriter; + + internal List NestedCommandSets; + + internal HelpCommand help; + + internal bool showHelp; + + internal OptionSet Options => options; + +#if !PCL || NETSTANDARD1_3 + public CommandSet(string suite, MessageLocalizerConverter localizer = null) + : this(suite, Console.Out, Console.Error, localizer) + { + } +#endif + + public CommandSet(string suite, TextWriter output, TextWriter error, MessageLocalizerConverter localizer = null) + { + if (suite == null) + throw new ArgumentNullException(nameof(suite)); + if (output == null) + throw new ArgumentNullException(nameof(output)); + if (error == null) + throw new ArgumentNullException(nameof(error)); + + this.suite = suite; + options = new CommandOptionSet(this, localizer); + outWriter = output; + errorWriter = error; + } + + public string Suite => suite; + public TextWriter Out => outWriter; + public TextWriter Error => errorWriter; + public MessageLocalizerConverter MessageLocalizer => options.MessageLocalizer; + + protected override string GetKeyForItem(Command item) + { + return item?.Name; + } + + public new CommandSet Add(Command value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + AddCommand(value); + options.Add(new CommandOption(value)); + return this; + } + + private void AddCommand(Command value) + { + if (value.CommandSet != null && value.CommandSet != this) + { + throw new ArgumentException("Command instances can only be added to a single CommandSet.", nameof(value)); + } + value.CommandSet = this; + if (value.Options != null) + { + value.Options.MessageLocalizer = options.MessageLocalizer; + } + + base.Add(value); + + help = help ?? value as HelpCommand; + } + + public CommandSet Add(string header) + { + options.Add(header); + return this; + } + + public CommandSet Add(Option option) + { + options.Add(option); + return this; + } + + public CommandSet Add(string prototype, Action action) + { + options.Add(prototype, action); + return this; + } + + public CommandSet Add(string prototype, string description, Action action) + { + options.Add(prototype, description, action); + return this; + } + + public CommandSet Add(string prototype, string description, Action action, bool hidden) + { + options.Add(prototype, description, action, hidden); + return this; + } + + public CommandSet Add(string prototype, OptionAction action) + { + options.Add(prototype, action); + return this; + } + + public CommandSet Add(string prototype, string description, OptionAction action) + { + options.Add(prototype, description, action); + return this; + } + + public CommandSet Add(string prototype, string description, OptionAction action, bool hidden) + { + options.Add(prototype, description, action, hidden); + return this; + } + + public CommandSet Add(string prototype, Action action) + { + options.Add(prototype, null, action); + return this; + } + + public CommandSet Add(string prototype, string description, Action action) + { + options.Add(prototype, description, action); + return this; + } + + public CommandSet Add(string prototype, OptionAction action) + { + options.Add(prototype, action); + return this; + } + + public CommandSet Add(string prototype, string description, OptionAction action) + { + options.Add(prototype, description, action); + return this; + } + + public CommandSet Add(ArgumentSource source) + { + options.Add(source); + return this; + } + + public CommandSet Add(CommandSet nestedCommands) + { + if (nestedCommands == null) + throw new ArgumentNullException(nameof(nestedCommands)); + + if (NestedCommandSets == null) + { + NestedCommandSets = new List(); + } + + if (!AlreadyAdded(nestedCommands)) + { + NestedCommandSets.Add(nestedCommands); + foreach (var o in nestedCommands.options) + { + if (o is CommandOption c) + { + options.Add(new CommandOption(c.Command, $"{nestedCommands.Suite} {c.CommandName}")); + } + else + { + options.Add(o); + } + } + } + + nestedCommands.options = this.options; + nestedCommands.outWriter = this.outWriter; + nestedCommands.errorWriter = this.errorWriter; + + return this; + } + + private bool AlreadyAdded(CommandSet value) + { + if (value == this) + return true; + if (NestedCommandSets == null) + return false; + foreach (var nc in NestedCommandSets) + { + if (nc.AlreadyAdded(value)) + return true; + } + return false; + } + + public IEnumerable GetCompletions(string prefix = null) + { + string rest; + ExtractToken(ref prefix, out rest); + + foreach (var command in this) + { + if (command.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + yield return command.Name; + } + } + + if (NestedCommandSets == null) + yield break; + + foreach (var subset in NestedCommandSets) + { + if (subset.Suite.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + foreach (var c in subset.GetCompletions(rest)) + { + yield return $"{subset.Suite} {c}"; + } + } + } + } + + private static void ExtractToken(ref string input, out string rest) + { + rest = ""; + input = input ?? ""; + + int top = input.Length; + for (int i = 0; i < top; i++) + { + if (char.IsWhiteSpace(input[i])) + continue; + + for (int j = i; j < top; j++) + { + if (char.IsWhiteSpace(input[j])) + { + rest = input.Substring(j).Trim(); + input = input.Substring(i, j).Trim(); + return; + } + } + rest = ""; + if (i != 0) + input = input.Substring(i).Trim(); + return; + } + } + + public int Run(IEnumerable arguments) + { + if (arguments == null) + throw new ArgumentNullException(nameof(arguments)); + + this.showHelp = false; + if (help == null) + { + help = new HelpCommand(); + AddCommand(help); + } + Action setHelp = v => showHelp = v != null; + if (!options.Contains("help")) + { + options.Add("help", "", setHelp, hidden: true); + } + if (!options.Contains("?")) + { + options.Add("?", "", setHelp, hidden: true); + } + var extra = options.Parse(arguments); + if (extra.Count == 0) + { + if (showHelp) + { + return help.Invoke(extra); + } + Out.WriteLine(options.MessageLocalizer($"Use `{Suite} help` for usage.")); + return 1; + } + var command = GetCommand(extra); + if (command == null) + { + help.WriteUnknownCommand(extra[0]); + return 1; + } + if (showHelp) + { + if (command.Options?.Contains("help") ?? true) + { + extra.Add("--help"); + return command.Invoke(extra); + } + command.Options.WriteOptionDescriptions(Out); + return 0; + } + return command.Invoke(extra); + } + + internal Command GetCommand(List extra) + { + return TryGetLocalCommand(extra) ?? TryGetNestedCommand(extra); + } + + private Command TryGetLocalCommand(List extra) + { + var name = extra[0]; + if (Contains(name)) + { + extra.RemoveAt(0); + return this[name]; + } + for (int i = 1; i < extra.Count; ++i) + { + name = name + " " + extra[i]; + if (!Contains(name)) + continue; + extra.RemoveRange(0, i + 1); + return this[name]; + } + return null; + } + + private Command TryGetNestedCommand(List extra) + { + if (NestedCommandSets == null) + return null; + + var nestedCommands = NestedCommandSets.Find(c => c.Suite == extra[0]); + if (nestedCommands == null) + return null; + + var extraCopy = new List(extra); + extraCopy.RemoveAt(0); + if (extraCopy.Count == 0) + return null; + + var command = nestedCommands.GetCommand(extraCopy); + if (command != null) + { + extra.Clear(); + extra.AddRange(extraCopy); + return command; + } + return null; + } + } + + public class HelpCommand : Command + { + public HelpCommand() + : base("help", help: "Show this message and exit") + { + } + + public override int Invoke(IEnumerable arguments) + { + var extra = new List(arguments ?? Array.Empty()); + var _ = CommandSet.Options.MessageLocalizer; + if (extra.Count == 0) + { + CommandSet.Options.WriteOptionDescriptions(CommandSet.Out); + return 0; + } + var command = CommandSet.GetCommand(extra); + if (command == this || extra.Contains("--help")) + { + CommandSet.Out.WriteLine(_($"Usage: {CommandSet.Suite} COMMAND [OPTIONS]")); + CommandSet.Out.WriteLine(_($"Use `{CommandSet.Suite} help COMMAND` for help on a specific command.")); + CommandSet.Out.WriteLine(); + CommandSet.Out.WriteLine(_($"Available commands:")); + CommandSet.Out.WriteLine(); + var commands = GetCommands(); + commands.Sort((x, y) => string.Compare(x.Key, y.Key, StringComparison.OrdinalIgnoreCase)); + foreach (var c in commands) + { + if (c.Key == "help") + { + continue; + } + CommandSet.Options.WriteCommandDescription(CommandSet.Out, c.Value, c.Key); + } + CommandSet.Options.WriteCommandDescription(CommandSet.Out, CommandSet.help, "help"); + return 0; + } + if (command == null) + { + WriteUnknownCommand(extra[0]); + return 1; + } + if (command.Options != null) + { + command.Options.WriteOptionDescriptions(CommandSet.Out); + return 0; + } + return command.Invoke(new[] { "--help" }); + } + + private List> GetCommands() + { + var commands = new List>(); + + foreach (var c in CommandSet) + { + commands.Add(new KeyValuePair(c.Name, c)); + } + + if (CommandSet.NestedCommandSets == null) + return commands; + + foreach (var nc in CommandSet.NestedCommandSets) + { + AddNestedCommands(commands, "", nc); + } + + return commands; + } + + private void AddNestedCommands(List> commands, string outer, CommandSet value) + { + foreach (var v in value) + { + commands.Add(new KeyValuePair($"{outer}{value.Suite} {v.Name}", v)); + } + if (value.NestedCommandSets == null) + return; + foreach (var nc in value.NestedCommandSets) + { + AddNestedCommands(commands, $"{outer}{value.Suite} ", nc); + } + } + + internal void WriteUnknownCommand(string unknownCommand) + { + CommandSet.Error.WriteLine(CommandSet.Options.MessageLocalizer($"{CommandSet.Suite}: Unknown command: {unknownCommand}")); + CommandSet.Error.WriteLine(CommandSet.Options.MessageLocalizer($"{CommandSet.Suite}: Use `{CommandSet.Suite} help` for usage.")); + } + } +} diff --git a/src/mono/wasm/host/Program.cs b/src/mono/wasm/host/Program.cs new file mode 100644 index 00000000000000..6441afa8a2727f --- /dev/null +++ b/src/mono/wasm/host/Program.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using Mono.Options; +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Threading; + +namespace Microsoft.WebAssembly.AppHost; + +public class WasmAppHost +{ + internal delegate Task HostHandler(CommonConfiguration commonArgs, + ILoggerFactory loggerFactory, + ILogger logger, + CancellationToken token); + + private static readonly Dictionary s_hostHandlers = new(); + + public static async Task Main(string[] args) + { + Console.WriteLine($"WasmAppHost {string.Join(' ', args)}"); + RegisterHostHandler(WasmHost.Browser, BrowserHost.InvokeAsync); + RegisterHostHandler(WasmHost.V8, JSEngineHost.InvokeAsync); + RegisterHostHandler(WasmHost.NodeJS, JSEngineHost.InvokeAsync); + + using CancellationTokenSource cts = new(); + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => + builder.AddSimpleConsole(options => + { + options.SingleLine = true; + options.TimestampFormat = "[HH:mm:ss] "; + }) + .AddFilter("DevToolsProxy", LogLevel.Information) + .AddFilter("FirefoxMonoProxy", LogLevel.Information) + .AddFilter("host", LogLevel.Trace) + .AddFilter(null, LogLevel.Warning)); + + ILogger logger = loggerFactory.CreateLogger("host"); + try + { + CommonConfiguration commonConfig = CommonConfiguration.FromCommandLineArguments(args); + return !s_hostHandlers.TryGetValue(commonConfig.Host, out HostHandler? handler) + ? throw new Exception($"Cannot find any handler for host type {commonConfig.Host}") + : await handler(commonConfig, loggerFactory, logger, cts.Token); + } + catch (OptionException oe) + { + Console.WriteLine($"Error: {oe.Message}"); + return -1; + } + } + + private static void RegisterHostHandler(WasmHost host, HostHandler handler) + => s_hostHandlers[host] = handler; +} diff --git a/src/mono/wasm/host/RunArgumentsJson.cs b/src/mono/wasm/host/RunArgumentsJson.cs new file mode 100644 index 00000000000000..eb612a8efd60de --- /dev/null +++ b/src/mono/wasm/host/RunArgumentsJson.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +#nullable enable + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed record RunArgumentsJson( + string[] applicationArguments, + string[]? runtimeArguments = null, + IDictionary? environmentVariables = null, + bool forwardConsole = false, + bool debugging = false +) +{ + // using an explicit property because the deserializer doesn't like + // extension data in the record constructor + [property: JsonExtensionData] public Dictionary? Extra { get; set; } + + public void Save(string file) + { + string json = JsonSerializer.Serialize(this, new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + File.WriteAllText(file, json); + } +} diff --git a/src/mono/wasm/host/RunConfiguration.cs b/src/mono/wasm/host/RunConfiguration.cs new file mode 100644 index 00000000000000..4d4b84a052798b --- /dev/null +++ b/src/mono/wasm/host/RunConfiguration.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.IO; +using System.Linq; +using System.Text.Json; +using Microsoft.WebAssembly.Diagnostics; + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed class RunConfiguration +{ + public WasmHost Host { get; init; } + public WasmHostProperties HostProperties { get; init; } + public HostConfig HostConfig { get; init; } + public string AppPath { get; init; } + + public RunConfiguration(string runtimeConfigPath, string? hostArg) + { + if (string.IsNullOrEmpty(runtimeConfigPath) || !File.Exists(runtimeConfigPath)) + throw new Exception($"Cannot find runtime config at {runtimeConfigPath}"); + + AppPath = Path.GetDirectoryName(runtimeConfigPath) ?? "."; + + RuntimeConfig? rconfig = JsonSerializer.Deserialize( + File.ReadAllText(runtimeConfigPath), + new JsonSerializerOptions(JsonSerializerDefaults.Web) + { + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip, + PropertyNameCaseInsensitive = true + }); + if (rconfig == null) + throw new Exception($"Failed to deserialize {runtimeConfigPath}"); + + if (rconfig.RuntimeOptions == null) + throw new Exception($"Failed to deserialize {runtimeConfigPath} - rconfig.RuntimeOptions"); + + HostProperties = rconfig.RuntimeOptions.WasmHostProperties; + if (HostProperties == null) + throw new Exception($"Failed to deserialize {runtimeConfigPath} - config"); + + if (HostProperties.HostConfigs is null || HostProperties.HostConfigs.Count == 0) + throw new Exception($"no perHostConfigs found"); + + // read only if it wasn't overridden by command line option + string desiredConfig = hostArg ?? HostProperties.DefaultConfig; + HostConfig? foundConfig = HostProperties.HostConfigs + .Where(hc => string.Equals(hc.Name, desiredConfig, StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefault(); + + HostConfig = foundConfig ?? HostProperties.HostConfigs.First(); + if (HostConfig == null) + throw new Exception("no host config found"); + + // FIXME: validate hostconfig + if (!Enum.TryParse(HostConfig.HostString, ignoreCase: true, out WasmHost wasmHost)) + throw new Exception($"Unknown host {HostConfig.HostString} in config named {HostConfig.Name}"); + Host = wasmHost; + } + + public ProxyOptions ToProxyOptions() + { + ProxyOptions options = new(); + if (HostProperties.ChromeProxyPort is not null) + options.DevToolsProxyPort = HostProperties.ChromeProxyPort.Value; + if (HostProperties.ChromeDebuggingPort is not null) + options.DevToolsDebugPort = HostProperties.ChromeDebuggingPort.Value; + if (HostProperties.FirefoxProxyPort is not null) + options.FirefoxProxyPort = HostProperties.FirefoxProxyPort.Value; + if (HostProperties.FirefoxDebuggingPort is not null) + options.FirefoxDebugPort = HostProperties.FirefoxDebuggingPort.Value; + options.LogPath = "."; + options.AutoSetBreakpointOnEntryPoint = true; + + return options; + } +} diff --git a/src/mono/wasm/host/RuntimeConfigJson.cs b/src/mono/wasm/host/RuntimeConfigJson.cs new file mode 100644 index 00000000000000..3b7b0564c4d09c --- /dev/null +++ b/src/mono/wasm/host/RuntimeConfigJson.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +#nullable enable + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed record RuntimeConfig(RuntimeOptions RuntimeOptions); + +internal sealed record RuntimeOptions(WasmHostProperties WasmHostProperties); + +internal sealed record WasmHostProperties( + string DefaultConfig, + [property: JsonPropertyName("perHostConfig")] List HostConfigs, + string MainAssembly, + string[] RuntimeArguments, + IDictionary? EnvironmentVariables, + int? FirefoxProxyPort, + int? FirefoxDebuggingPort, + int? ChromeProxyPort, + int? ChromeDebuggingPort, + int WebServerPort = 9000) +{ + // using an explicit property because the deserializer doesn't like + // extension data in the record constructor + [property: JsonExtensionData] public Dictionary? Extra { get; set; } +} + +internal sealed record HostConfig(string? Name, [property: JsonPropertyName("host")] string? HostString) +{ + // using an explicit property because the deserializer doesn't like + // extension data in the record constructor + [property: JsonExtensionData] public Dictionary? Properties { get; set; } +} diff --git a/src/mono/wasm/host/Utils.cs b/src/mono/wasm/host/Utils.cs new file mode 100644 index 00000000000000..916fd999f374e5 --- /dev/null +++ b/src/mono/wasm/host/Utils.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Microsoft.WebAssembly.AppHost; + +#nullable enable + +public class Utils +{ + public static async Task TryRunProcess( + ProcessStartInfo psi, + ILogger logger, + Action? logStdOut = null, + Action? logStdErr = null, + string? label = null) + { + string msgPrefix = label == null ? string.Empty : $"[{label}] "; + logger.LogInformation($"{msgPrefix}Running: {psi.FileName} {string.Join(" ", psi.ArgumentList)}"); + + psi.UseShellExecute = false; + psi.CreateNoWindow = true; + if (logStdOut != null) + psi.RedirectStandardOutput = true; + if (logStdErr != null) + psi.RedirectStandardError = true; + + logger.LogDebug($"{msgPrefix}Using working directory: {psi.WorkingDirectory ?? Environment.CurrentDirectory}", msgPrefix); + + // if (psi.EnvironmentVariables.Count > 0) + // logger.LogDebug($"{msgPrefix}Setting environment variables for execution:", msgPrefix); + + // foreach (string key in psi.EnvironmentVariables.Keys) + // logger.LogDebug($"{msgPrefix}\t{key} = {psi.EnvironmentVariables[key]}"); + + Process? process = Process.Start(psi); + if (process == null) + throw new ArgumentException($"{msgPrefix}Process.Start({psi.FileName} {string.Join(" ", psi.ArgumentList)}) returned null process"); + + if (logStdErr != null) + process.ErrorDataReceived += (sender, e) => logStdErr!.Invoke(e.Data); + if (logStdOut != null) + process.OutputDataReceived += (sender, e) => logStdOut!.Invoke(e.Data); + + if (logStdOut != null) + process.BeginOutputReadLine(); + if (logStdErr != null) + process.BeginErrorReadLine(); + + await process.WaitForExitAsync(); + return process.ExitCode; + } +} diff --git a/src/mono/wasm/host/WasmAppHost.csproj b/src/mono/wasm/host/WasmAppHost.csproj new file mode 100644 index 00000000000000..45a3fcb2d61352 --- /dev/null +++ b/src/mono/wasm/host/WasmAppHost.csproj @@ -0,0 +1,25 @@ + + + + $(AspNetCoreAppCurrent) + true + $(NoWarn),CA2007 + enable + LatestMajor + + + + + + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\BrowserDebugHost.dll" /> + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\BrowserDebugHost.runtimeconfig.json" /> + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\BrowserDebugProxy.dll" /> + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.dll" /> + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.dll" /> + <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Newtonsoft.Json.dll" /> + + + + + + diff --git a/src/mono/wasm/host/WasmHost.cs b/src/mono/wasm/host/WasmHost.cs new file mode 100644 index 00000000000000..c137d1e60440eb --- /dev/null +++ b/src/mono/wasm/host/WasmHost.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.WebAssembly.AppHost; + +internal enum WasmHost +{ + /// + /// V8 + /// + V8, + /// + /// JavaScriptCore + /// + JavaScriptCore, + /// + /// SpiderMonkey + /// + SpiderMonkey, + /// + /// NodeJS + /// + NodeJS, + + Browser +} diff --git a/src/mono/wasm/host/WasmLogMessage.cs b/src/mono/wasm/host/WasmLogMessage.cs new file mode 100644 index 00000000000000..82f7e5200eec56 --- /dev/null +++ b/src/mono/wasm/host/WasmLogMessage.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.WebAssembly.AppHost; + +#nullable enable + +internal sealed class WasmLogMessage +{ + public string? method { get; set; } + public string? payload { get; set; } +} diff --git a/src/mono/wasm/host/WasmTestMessagesProcessor.cs b/src/mono/wasm/host/WasmTestMessagesProcessor.cs new file mode 100644 index 00000000000000..bfdd297a5f46c9 --- /dev/null +++ b/src/mono/wasm/host/WasmTestMessagesProcessor.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; +using Microsoft.Extensions.Logging; + +#nullable enable + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed class WasmTestMessagesProcessor +{ + private readonly ILogger _logger; + + public WasmTestMessagesProcessor(ILogger logger) + { + _logger = logger; + } + + public void Invoke(string message) + { + WasmLogMessage? logMessage = null; + string line; + + if (message.Length > 0 && message[0] == '{') + { + try + { + logMessage = JsonSerializer.Deserialize(message); + line = logMessage?.payload ?? message.TrimEnd(); + } + catch (JsonException) + { + line = message.TrimEnd(); + } + } + else + { + line = message.TrimEnd(); + } + + switch (logMessage?.method?.ToLowerInvariant()) + { + case "console.debug": _logger.LogDebug(line); break; + case "console.error": _logger.LogError(line); break; + case "console.warn": _logger.LogWarning(line); break; + case "console.trace": _logger.LogTrace(line); break; + + case "console.log": + default: _logger.LogInformation(line); break; + } + } +} diff --git a/src/mono/wasm/host/WebServer.cs b/src/mono/wasm/host/WebServer.cs new file mode 100644 index 00000000000000..51bda60716740d --- /dev/null +++ b/src/mono/wasm/host/WebServer.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +#nullable enable + +namespace Microsoft.WebAssembly.AppHost; + +public class WebServer +{ + internal static async Task<(ServerURLs, IWebHost)> StartAsync(WebServerOptions options, ILogger logger, CancellationToken token) + { + string[]? urls = new string[] { $"http://127.0.0.1:{options.Port}", "https://127.0.0.1:0" }; + + IWebHostBuilder builder = new WebHostBuilder() + .UseKestrel() + .UseStartup() + .ConfigureLogging(logging => + { + logging.AddConsole().AddFilter(null, LogLevel.Warning); + }) + .ConfigureServices((ctx, services) => + { + if (options.WebServerUseCors) + { + services.AddCors(o => o.AddPolicy("AnyCors", builder => + { + builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + .WithExposedHeaders("*"); + })); + } + services.AddSingleton(logger); + services.AddSingleton(Options.Create(options)); + services.AddRouting(); + }) + .UseUrls(urls); + + if (options.ContentRootPath != null) + builder.UseContentRoot(options.ContentRootPath); + + IWebHost? host = builder.Build(); + await host.StartAsync(token); + + ICollection? addresses = host.ServerFeatures + .Get()? + .Addresses; + + string? ipAddress = + addresses? + .Where(a => a.StartsWith("http:", StringComparison.InvariantCultureIgnoreCase)) + .Select(a => new Uri(a)) + .Select(uri => uri.ToString()) + .FirstOrDefault(); + + string? ipAddressSecure = + addresses? + .Where(a => a.StartsWith("https:", StringComparison.OrdinalIgnoreCase)) + .Select(a => new Uri(a)) + .Select(uri => uri.ToString()) + .FirstOrDefault(); + + return ipAddress == null || ipAddressSecure == null + ? throw new InvalidOperationException("Failed to determine web server's IP address or port") + : (new ServerURLs(ipAddress, ipAddressSecure), host); + } + +} + +// FIXME: can be simplified to string[] +public record ServerURLs(string Http, string? Https); diff --git a/src/mono/wasm/host/WebServerOptions.cs b/src/mono/wasm/host/WebServerOptions.cs new file mode 100644 index 00000000000000..bc8b5f2acee63a --- /dev/null +++ b/src/mono/wasm/host/WebServerOptions.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net.WebSockets; +using System.Threading.Tasks; + +#nullable enable + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed record WebServerOptions +( + Func? OnConsoleConnected, + string? ContentRootPath, + bool WebServerUseCors, + bool WebServerUseCrossOriginPolicy, + int Port, + string DefaultFileName = "index.html" +); diff --git a/src/mono/wasm/host/WebServerStartup.cs b/src/mono/wasm/host/WebServerStartup.cs new file mode 100644 index 00000000000000..ae5e906c518a65 --- /dev/null +++ b/src/mono/wasm/host/WebServerStartup.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.WebSockets; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; + +#nullable enable + +namespace Microsoft.WebAssembly.AppHost; + +internal sealed class WebServerStartup +{ + private readonly IWebHostEnvironment _hostingEnvironment; + + public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment; + + public void Configure(IApplicationBuilder app, IOptions optionsContainer) + { + var provider = new FileExtensionContentTypeProvider(); + provider.Mappings[".wasm"] = "application/wasm"; + provider.Mappings[".cjs"] = "text/javascript"; + provider.Mappings[".mjs"] = "text/javascript"; + + foreach (string extn in new string[] { ".dll", ".pdb", ".dat", ".blat" }) + { + provider.Mappings[extn] = "application/octet-stream"; + } + + WebServerOptions options = optionsContainer.Value; + if (options.WebServerUseCrossOriginPolicy) + { + app.Use((context, next) => + { + context.Response.Headers.Add("Cross-Origin-Embedder-Policy", "require-corp"); + context.Response.Headers.Add("Cross-Origin-Opener-Policy", "same-origin"); + return next(); + }); + } + + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath), + ContentTypeProvider = provider, + ServeUnknownFileTypes = true + }); + + if (options.WebServerUseCors) + { + app.UseCors("AnyCors"); + } + app.UseRouting(); + app.UseWebSockets(); + if (options.OnConsoleConnected is not null) + { + app.UseRouter(router => + { + router.MapGet("/console", async context => + { + if (!context.WebSockets.IsWebSocketRequest) + { + context.Response.StatusCode = 400; + return; + } + + using WebSocket socket = await context.WebSockets.AcceptWebSocketAsync(); + await options.OnConsoleConnected(socket); + }); + }); + } + + // app.UseEndpoints(endpoints => + // { + // endpoints.MapFallbackToFile(options.DefaultFileName); + // }); + } +} diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index a24b168e8454ab..9395d6bd774f98 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -471,7 +471,7 @@ async function mono_download_assets(config: MonoConfig | MonoConfigError | undef runtimeHelpers.fetch = (config).fetch_file_cb; } - const max_parallel_downloads = 100; + const max_parallel_downloads = 10; // in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time let parallel_count = 0; let throttling_promise: Promise | undefined = undefined; diff --git a/src/mono/wasm/templates/templates/browser/Program.cs b/src/mono/wasm/templates/templates/browser/Program.cs index 5bfeaa26ef7af9..3e358139c759a8 100644 --- a/src/mono/wasm/templates/templates/browser/Program.cs +++ b/src/mono/wasm/templates/templates/browser/Program.cs @@ -1,12 +1,3 @@ using System; -using System.Runtime.CompilerServices; Console.WriteLine ("Hello, Browser!"); - -public class MyClass { - [MethodImpl(MethodImplOptions.NoInlining)] - public static string CallMeFromJS() - { - return "Hello, World!"; - } -} diff --git a/src/mono/wasm/templates/templates/browser/app-support.js b/src/mono/wasm/templates/templates/browser/app-support.js new file mode 100644 index 00000000000000..316ecaa07865f7 --- /dev/null +++ b/src/mono/wasm/templates/templates/browser/app-support.js @@ -0,0 +1,243 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// -*- mode: js; js-indent-level: 4; -*- +// +"use strict"; + +const is_browser = typeof window != "undefined"; +if (!is_browser) + throw new Error(`Expected to be running in a browser`); + +export const App = {}; + +const originalConsole = { + log: console.log, + error: console.error +}; + +function proxyConsoleMethod(prefix, func, asJson) { + return function () { + try { + const args = [...arguments]; + let payload = args[0]; + if (payload === undefined) payload = 'undefined'; + else if (payload === null) payload = 'null'; + else if (typeof payload === 'function') payload = payload.toString(); + else if (typeof payload !== 'string') { + try { + payload = JSON.stringify(payload); + } catch (e) { + payload = payload.toString(); + } + } + + if (asJson) { + func(JSON.stringify({ + method: prefix, + payload: payload, + arguments: args + })); + } else { + func([prefix + payload, ...args.slice(1)]); + } + } catch (err) { + originalConsole.error(`proxyConsole failed: ${err}`) + } + }; +}; + +function set_exit_code(exit_code, reason) { + if (reason) { + if (reason instanceof Error) + console.error(stringify_as_error_with_stack(reason)); + else if (typeof reason == "string") + console.error(reason); + else + console.error(JSON.stringify(reason)); + } + + if (forward_console) { + const stop_when_ws_buffer_empty = () => { + if (consoleWebSocket.bufferedAmount == 0) { + // tell xharness WasmTestMessagesProcessor we are done. + // note this sends last few bytes into the same WS + console.log("WASM EXIT " + exit_code); + } + else { + setTimeout(stop_when_ws_buffer_empty, 100); + } + }; + stop_when_ws_buffer_empty(); + } else { + console.log("WASM EXIT " + exit_code); + } +} + +function stringify_as_error_with_stack(err) { + if (!err) + return ""; + + // FIXME: + if (App && App.INTERNAL) + return App.INTERNAL.mono_wasm_stringify_as_error_with_stack(err); + + if (err.stack) + return err.stack; + + if (typeof err == "string") + return err; + + return JSON.stringify(err); +} + +let runArgs = {}; +let consoleWebSocket; +let is_debugging = false; +let forward_console = false; + +function initRunArgs() { + // set defaults + runArgs.applicationArguments = runArgs.applicationArguments === undefined ? [] : runArgs.applicationArguments; + runArgs.workingDirectory = runArgs.workingDirectory === undefined ? '/' : runArgs.workingDirectory; + runArgs.environment_variables = runArgs.environment_variables === undefined ? {} : runArgs.environment_variables; + runArgs.runtimeArgs = runArgs.runtimeArgs === undefined ? [] : runArgs.runtimeArgs; + runArgs.enableGC = runArgs.enableGC === undefined ? true : runArgs.enableGC; + runArgs.diagnosticTracing = runArgs.diagnosticTracing === undefined ? false : runArgs.diagnosticTracing; + runArgs.debugging = runArgs.debugging === undefined ? false : runArgs.debugging; + runArgs.forwardConsole = runArgs.forwardConsole === undefined ? false : runArgs.forwardConsole; +} + +function applyArguments() { + initRunArgs(); + + is_debugging = runArgs.debugging === true; + forward_console = runArgs.forwardConsole === true; + + if (forward_console) { + const methods = ["debug", "trace", "warn", "info", "error"]; + for (let m of methods) { + if (typeof (console[m]) !== "function") { + console[m] = proxyConsoleMethod(`console.${m}: `, console.log, false); + } + } + + const consoleUrl = `${window.location.origin}/console`.replace('http://', 'ws://'); + + consoleWebSocket = new WebSocket(consoleUrl); + consoleWebSocket.onopen = function (event) { + originalConsole.log("browser: Console websocket connected."); + }; + consoleWebSocket.onerror = function (event) { + originalConsole.error(`websocket error: ${event}`, event); + }; + consoleWebSocket.onclose = function (event) { + originalConsole.error(`websocket closed: ${event}`, event); + }; + + const send = (msg) => { + if (consoleWebSocket.readyState === WebSocket.OPEN) { + consoleWebSocket.send(msg); + } + else { + originalConsole.log(msg); + } + } + + // redirect output early, so that when emscripten starts it's already redirected + for (let m of ["log", ...methods]) + console[m] = proxyConsoleMethod(`console.${m}`, send, true); + } +} + +let toAbsoluteUrl = function(possiblyRelativeUrl) { return possiblyRelativeUrl; } +const anchorTagForAbsoluteUrlConversions = document.createElement('a'); +toAbsoluteUrl = function toAbsoluteUrl(possiblyRelativeUrl) { + anchorTagForAbsoluteUrlConversions.href = possiblyRelativeUrl; + return anchorTagForAbsoluteUrlConversions.href; +} + +let loadDotnetPromise = import('/dotnet.js'); +let argsPromise = fetch('/runArgs.json') + .then(async (response) => { + if (!response.ok) { + console.debug(`could not load /args.json: ${response.status}. Ignoring`); + } else { + runArgs = await response.json(); + console.debug(`runArgs: ${JSON.stringify(runArgs)}`); + } + }) + .catch(error => console.error(`Failed to load args: ${stringify_as_error_with_stack(error)}`)); + +Promise.all([ argsPromise, loadDotnetPromise ]).then(async ([ _, { default: createDotnetRuntime } ]) => { + applyArguments(); + + return createDotnetRuntime(({ MONO, INTERNAL, BINDING, Module }) => ({ + disableDotnet6Compatibility: true, + config: null, + configSrc: "./mono-config.json", + locateFile: (path, prefix) => { + return toAbsoluteUrl(prefix + path); + }, + onConfigLoaded: (config) => { + if (!Module.config) { + const err = new Error("Could not find ./mono-config.json. Cancelling run"); + set_exit_code(1); + throw err; + } + // Have to set env vars here to enable setting MONO_LOG_LEVEL etc. + for (let variable in runArgs.runtimeArgs) { + config.environment_variables[variable] = runArgs.runtimeArgs[variable]; + } + config.diagnostic_tracing = !!runArgs.diagnosticTracing; + if (is_debugging) { + if (config.debug_level == 0) + config.debug_level = -1; + + config.wait_for_debugger = -1; + } + }, + preRun: () => { + if (!runArgs.enableGC) { + INTERNAL.mono_wasm_enable_on_demand_gc(0); + } + }, + onDotnetReady: () => { + let wds = Module.FS.stat(runArgs.workingDirectory); + if (wds === undefined || !Module.FS.isDir(wds.mode)) { + set_exit_code(1, `Could not find working directory ${runArgs.working_dir}`); + return; + } + + Module.FS.chdir(runArgs.workingDirectory); + + if (runArgs.runtimeArgs.length > 0) + INTERNAL.mono_wasm_set_runtime_options(runArgs.runtimeArgs); + + console.info("Initializing....."); + Object.assign(App, { MONO, INTERNAL, BINDING, Module, runArgs }); + + try { + if (App.init) + { + let ret = App.init(); + Promise.resolve(ret).then(function (code) { set_exit_code(code ?? 0); }); + } + else + { + console.log("WASM ERROR: no App.init defined"); + set_exit_code(1, "WASM ERROR: no App.init defined"); + } + } catch (err) { + console.log(`WASM ERROR ${err}`); + if (is_browser && document.getElementById("out")) + document.getElementById("out").innerHTML = `error: ${err}`; + set_exit_code(1, err); + } + }, + onAbort: (error) => { + set_exit_code(1, stringify_as_error_with_stack(new Error())); + }, + })); +}).catch(function (err) { + set_exit_code(1, "failed to load the dotnet.js file.\n" + stringify_as_error_with_stack(err)); +}); diff --git a/src/mono/wasm/templates/templates/browser/browser.0.csproj b/src/mono/wasm/templates/templates/browser/browser.0.csproj index 6b36bc95f7f585..06f63d8ec4a987 100644 --- a/src/mono/wasm/templates/templates/browser/browser.0.csproj +++ b/src/mono/wasm/templates/templates/browser/browser.0.csproj @@ -2,16 +2,18 @@ net7.0 wasm - Browser + browser browser-wasm true main.js Exe true + true + diff --git a/src/mono/wasm/templates/templates/browser/main.js b/src/mono/wasm/templates/templates/browser/main.js index 0a3d6e95c20ff2..1a999e00ed9afe 100644 --- a/src/mono/wasm/templates/templates/browser/main.js +++ b/src/mono/wasm/templates/templates/browser/main.js @@ -1,13 +1,5 @@ -import createDotnetRuntime from './dotnet.js' +import { App } from './app-support.js' -try { - const { MONO, BINDING, Module, RuntimeBuildInfo } = await createDotnetRuntime(); - const managedMethod = BINDING.bind_static_method("[browser.0] MyClass:CallMeFromJS"); - const text = managedMethod(); - document.getElementById("out").innerHTML = `${text}`; - - await MONO.mono_run_main("browser.0.dll", []); -} catch (err) { - console.log(`WASM ERROR ${err}`); - document.getElementById("out").innerHTML = `error: ${err}`; +App.init = async function () { + await App.MONO.mono_run_main("browser.0.dll", App.runArgs.applicationArguments); } diff --git a/src/mono/wasm/templates/templates/browser/runtimeconfig.template.json b/src/mono/wasm/templates/templates/browser/runtimeconfig.template.json new file mode 100644 index 00000000000000..8f0557352c6ed3 --- /dev/null +++ b/src/mono/wasm/templates/templates/browser/runtimeconfig.template.json @@ -0,0 +1,11 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "browser", + "html-path": "index.html", + "Host": "browser" + } + ] + } +} diff --git a/src/mono/wasm/templates/templates/console/Program.cs b/src/mono/wasm/templates/templates/console/Program.cs index 3a242248ba2090..87607cda6afc59 100644 --- a/src/mono/wasm/templates/templates/console/Program.cs +++ b/src/mono/wasm/templates/templates/console/Program.cs @@ -1,9 +1,3 @@ using System; -using System.Threading.Tasks; -Console.WriteLine("Hello World!"); - -Console.WriteLine("Args:"); -for (int i = 0; i < args.Length; i++) { - Console.WriteLine($" args[{i}] = {args[i]}"); -} +Console.WriteLine("Hello, Console!"); diff --git a/src/mono/wasm/templates/templates/console/app-support.cjs b/src/mono/wasm/templates/templates/console/app-support.cjs new file mode 100644 index 00000000000000..073df84f1e084e --- /dev/null +++ b/src/mono/wasm/templates/templates/console/app-support.cjs @@ -0,0 +1,253 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// -*- mode: js; js-indent-level: 4; -*- +// +"use strict"; + +//glue code to deal with the differences between chrome, ch, d8, jsc and sm. +const is_browser = false; +const is_node = !is_browser && typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string'; +const App = {}; + +if (!is_node) + throw new Error(`This file only supports nodejs`); + +if (is_node && process.versions.node.split(".")[0] < 14) { + throw new Error(`NodeJS at '${process.execPath}' has too low version '${process.versions.node}'`); +} + +function stringify_as_error_with_stack(err) { + if (!err) + return ""; + + // FIXME: + if (App && App.INTERNAL) + return App.INTERNAL.mono_wasm_stringify_as_error_with_stack(err); + + if (err.stack) + return err.stack; + + if (typeof err == "string") + return err; + + return JSON.stringify(err); +} + +function set_exit_code(exit_code, reason) { + if (reason) { + if (reason instanceof Error) + console.error(stringify_as_error_with_stack(reason)); + else if (typeof reason == "string") + console.error(reason); + else + console.error(JSON.stringify(reason)); + } + + if (App && App.INTERNAL) { + App.INTERNAL.mono_wasm_exit(exit_code); + } +} + +let processedArguments = null; +let is_debugging = false; + +function processArguments(incomingArguments) { + console.log("Incoming arguments: " + incomingArguments.join(' ')); + let profilers = []; + let setenv = {}; + let runtime_args = []; + let enable_gc = true; + let diagnostic_tracing = false; + let working_dir = '/'; + + while (incomingArguments && incomingArguments.length > 0) { + const currentArg = incomingArguments[0]; + if (currentArg.startsWith("--profile=")) { + const arg = currentArg.substring("--profile=".length); + profilers.push(arg); + } else if (currentArg.startsWith("--setenv=")) { + const arg = currentArg.substring("--setenv=".length); + const parts = arg.split('='); + if (parts.length != 2) + set_exit_code(1, "Error: malformed argument: '" + currentArg); + setenv[parts[0]] = parts[1]; + } else if (currentArg.startsWith("--runtime-arg=")) { + const arg = currentArg.substring("--runtime-arg=".length); + runtime_args.push(arg); + } else if (currentArg == "--disable-on-demand-gc") { + enable_gc = false; + } else if (currentArg == "--diagnostic_tracing") { + diagnostic_tracing = true; + } else if (currentArg.startsWith("--working-dir=")) { + const arg = currentArg.substring("--working-dir=".length); + working_dir = arg; + } else if (currentArg == "--debug") { + is_debugging = true; + } else { + break; + } + incomingArguments = incomingArguments.slice(1); + } + + // cheap way to let the testing infrastructure know we're running in a browser context (or not) + setenv["IsBrowserDomSupported"] = is_browser.toString().toLowerCase(); + setenv["IsNodeJS"] = is_node.toString().toLowerCase(); + + console.log("Application arguments: " + incomingArguments.join(' ')); + + return { + applicationArgs: incomingArguments, + profilers, + setenv, + runtime_args, + enable_gc, + diagnostic_tracing, + working_dir, + } +} + +async function loadDotnet(file) { + let loadScript = undefined; + if (typeof WScript !== "undefined") { // Chakra + loadScript = function (file) { + WScript.LoadScriptFile(file); + return globalThis.createDotnetRuntime; + }; + } else if (is_node) { // NodeJS + loadScript = async function (file) { + return require(file); + }; + } else if (typeof globalThis.load !== 'undefined') { + loadScript = async function (file) { + globalThis.load(file) + return globalThis.createDotnetRuntime; + } + } + else { + throw new Error("Unknown environment, can't load config"); + } + + return loadScript(file); +} + +// this can't be function because of `arguments` scope +try { + if (is_node) { + processedArguments = processArguments(process.argv.slice(2)); + } else if (typeof arguments !== "undefined") { + processedArguments = processArguments(Array.from(arguments)); + } else if (typeof scriptArgs !== "undefined") { + processedArguments = processArguments(Array.from(scriptArgs)); + } else if (typeof WScript !== "undefined" && WScript.Arguments) { + processedArguments = processArguments(Array.from(WScript.Arguments)); + } +} catch (e) { + console.error(e); +} + +if (is_node) { + const modulesToLoad = processedArguments.setenv["NPM_MODULES"]; + if (modulesToLoad) { + modulesToLoad.split(',').forEach(module => { + const { 0:moduleName, 1:globalAlias } = module.split(':'); + + let message = `Loading npm '${moduleName}'`; + let moduleExport = require(moduleName); + + if (globalAlias) { + message += ` and attaching to global as '${globalAlias}'`; + globalThis[globalAlias] = moduleExport; + } else if(moduleName == "node-fetch") { + message += ' and attaching to global'; + globalThis.fetch = moduleExport.default; + globalThis.Headers = moduleExport.Headers; + globalThis.Request = moduleExport.Request; + globalThis.Response = moduleExport.Response; + } else if(moduleName == "node-abort-controller") { + message += ' and attaching to global'; + globalThis.AbortController = moduleExport.AbortController; + } + + console.log(message); + }); + } +} + +if (is_node) { + module.exports.App = App; + module.exports.is_browser = is_browser; + module.exports.is_node = is_node; + module.exports.set_exit_code = set_exit_code; +} + +let toAbsoluteUrl = function(possiblyRelativeUrl) { return possiblyRelativeUrl; } + +// Must be after loading npm modules. +processedArguments.setenv["IsWebSocketSupported"] = ("WebSocket" in globalThis).toString().toLowerCase(); + +loadDotnet("./dotnet.js").then((createDotnetRuntime) => { + return createDotnetRuntime(({ MONO, INTERNAL, BINDING, Module }) => ({ + disableDotnet6Compatibility: true, + config: null, + configSrc: "./mono-config.json", + locateFile: (path, prefix) => { + return toAbsoluteUrl(prefix + path); + }, + onConfigLoaded: (config) => { + if (!Module.config) { + const err = new Error("Could not find ./mono-config.json. Cancelling run"); + set_exit_code(1); + throw err; + } + // Have to set env vars here to enable setting MONO_LOG_LEVEL etc. + for (let variable in processedArguments.setenv) { + config.environment_variables[variable] = processedArguments.setenv[variable]; + } + config.diagnostic_tracing = !!processedArguments.diagnostic_tracing; + if (is_debugging && config.debug_level == 0) + config.debug_level = -1; + }, + preRun: () => { + if (!processedArguments.enable_gc) { + INTERNAL.mono_wasm_enable_on_demand_gc(0); + } + }, + onDotnetReady: () => { + let wds = Module.FS.stat(processedArguments.working_dir); + if (wds === undefined || !Module.FS.isDir(wds.mode)) { + set_exit_code(1, `Could not find working directory ${processedArguments.working_dir}`); + return; + } + + Module.FS.chdir(processedArguments.working_dir); + + if (processedArguments.runtime_args.length > 0) + INTERNAL.mono_wasm_set_runtime_options(processedArguments.runtime_args); + + console.info("Initializing....."); + Object.assign(App, { MONO, INTERNAL, BINDING, Module, processedArguments }); + + try { + if (App.init) + { + let ret = App.init(); + Promise.resolve(ret).then(function (code) { set_exit_code(code ?? 0); }); + } + else + { + console.log("WASM ERROR: no App.init defined"); + set_exit_code(1, "WASM ERROR: no App.init defined"); + } + } catch (err) { + console.log(`WASM ERROR ${err}`); + set_exit_code(1, err); + } + }, + onAbort: (error) => { + set_exit_code(1, stringify_as_error_with_stack(new Error())); + }, + })) +}).catch(function (err) { + set_exit_code(1, "failed to load the dotnet.js file.\n" + stringify_as_error_with_stack(err)); +}); + diff --git a/src/mono/wasm/templates/templates/console/console.0.csproj b/src/mono/wasm/templates/templates/console/console.0.csproj index fad622389dc305..6de7154fc2d721 100644 --- a/src/mono/wasm/templates/templates/console/console.0.csproj +++ b/src/mono/wasm/templates/templates/console/console.0.csproj @@ -2,11 +2,16 @@ net7.0 wasm - Browser + browser browser-wasm true main.cjs Exe false + true + + + + diff --git a/src/mono/wasm/templates/templates/console/main.cjs b/src/mono/wasm/templates/templates/console/main.cjs index cbc74495a4f2c3..e7909bbc71863d 100644 --- a/src/mono/wasm/templates/templates/console/main.cjs +++ b/src/mono/wasm/templates/templates/console/main.cjs @@ -1,8 +1,5 @@ -const createDotnetRuntime = require("./dotnet.js"); +const { App } = require("./app-support.cjs"); -async function main() { - const { MONO } = await createDotnetRuntime(); - const app_args = process.argv.slice(2); - await MONO.mono_run_main_and_exit("console.0.dll", app_args); -}; -main(); +App.init = async function () { + await App.MONO.mono_run_main_and_exit("console.0.dll", App.processedArguments.applicationArgs); +} diff --git a/src/mono/wasm/templates/templates/console/runtimeconfig.template.json b/src/mono/wasm/templates/templates/console/runtimeconfig.template.json new file mode 100644 index 00000000000000..6987a93b4fa496 --- /dev/null +++ b/src/mono/wasm/templates/templates/console/runtimeconfig.template.json @@ -0,0 +1,16 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "node", + "js-path": "main.cjs", + "Host": "nodejs" + }, + { + "name": "v8", + "js-path": "main.cjs", + "Host": "v8" + } + ] + } +} diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index 1cc3f645d8e5ef..39496b2982286c 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -26,6 +26,11 @@ const originalConsole = { error: console.error }; +let runArgs = {}; +let consoleWebSocket; +let is_debugging = false; +let forward_console = true; + function proxyConsoleMethod(prefix, func, asJson) { return function () { try { @@ -61,47 +66,54 @@ function proxyConsoleMethod(prefix, func, asJson) { }; }; -const methods = ["debug", "trace", "warn", "info", "error"]; -for (let m of methods) { - if (typeof (console[m]) !== "function") { - console[m] = proxyConsoleMethod(`console.${m}: `, console.log, false); +function set_exit_code(exit_code, reason) { + if (reason) { + if (reason instanceof Error) + console.error(stringify_as_error_with_stack(reason)); + else if (typeof reason == "string") + console.error(reason); + else + console.error(JSON.stringify(reason)); } -} - -let consoleWebSocket; -if (is_browser) { - const consoleUrl = `${window.location.origin}/console`.replace('http://', 'ws://'); + if (is_browser) { + if (App.Module) { + // Notify the selenium script + App.Module.exit_code = exit_code; + } - consoleWebSocket = new WebSocket(consoleUrl); - consoleWebSocket.onopen = function (event) { - originalConsole.log("browser: Console websocket connected."); - }; - consoleWebSocket.onerror = function (event) { - originalConsole.error(`websocket error: ${event}`); - }; - consoleWebSocket.onclose = function (event) { - originalConsole.error(`websocket closed: ${event}`); - }; + //Tell xharness WasmBrowserTestRunner what was the exit code + const tests_done_elem = document.createElement("label"); + tests_done_elem.id = "tests_done"; + tests_done_elem.innerHTML = exit_code.toString(); + document.body.appendChild(tests_done_elem); - const send = (msg) => { - if (consoleWebSocket.readyState === WebSocket.OPEN) { - consoleWebSocket.send(msg); - } - else { - originalConsole.log(msg); + if (forward_console) { + const stop_when_ws_buffer_empty = () => { + if (consoleWebSocket.bufferedAmount == 0) { + // tell xharness WasmTestMessagesProcessor we are done. + // note this sends last few bytes into the same WS + console.log("WASM EXIT " + exit_code); + } + else { + setTimeout(stop_when_ws_buffer_empty, 100); + } + }; + stop_when_ws_buffer_empty(); + } else { + console.log("WASM EXIT " + exit_code); } - } - // redirect output early, so that when emscripten starts it's already redirected - for (let m of ["log", ...methods]) - console[m] = proxyConsoleMethod(`console.${m}`, send, true); + } else if (App && App.INTERNAL) { + App.INTERNAL.mono_wasm_exit(exit_code); + } } function stringify_as_error_with_stack(err) { if (!err) return ""; + // FIXME: if (App && App.INTERNAL) return App.INTERNAL.mono_wasm_stringify_as_error_with_stack(err); @@ -114,6 +126,213 @@ function stringify_as_error_with_stack(err) { return JSON.stringify(err); } +function initRunArgs() { + // set defaults + runArgs.applicationArguments = runArgs.applicationArguments === undefined ? [] : runArgs.applicationArguments; + runArgs.profilers = runArgs.profilers === undefined ? [] : runArgs.profilers; + runArgs.workingDirectory = runArgs.workingDirectory === undefined ? '/' : runArgs.workingDirectory; + runArgs.environment_variables = runArgs.environment_variables === undefined ? {} : runArgs.environment_variables; + runArgs.runtimeArgs = runArgs.runtimeArgs === undefined ? [] : runArgs.runtimeArgs; + runArgs.enableGC = runArgs.enableGC === undefined ? true : runArgs.enableGC; + runArgs.diagnosticTracing = runArgs.diagnosticTracing === undefined ? false : runArgs.diagnosticTracing; + runArgs.debugging = runArgs.debugging === undefined ? false : runArgs.debugging; + // default'ing to true for tests, unless debugging + runArgs.forwardConsole = runArgs.forwardConsole === undefined ? !runArgs.debugging : runArgs.forwardConsole; +} + +function processQueryArguments(incomingArguments) { + initRunArgs(); + + console.log("Incoming arguments: " + incomingArguments.join(' ')); + while (incomingArguments && incomingArguments.length > 0) { + const currentArg = incomingArguments[0]; + if (currentArg.startsWith("--profile=")) { + const arg = currentArg.substring("--profile=".length); + runArgs.profilers.push(arg); + } else if (currentArg.startsWith("--setenv=")) { + const arg = currentArg.substring("--setenv=".length); + const parts = arg.split('='); + if (parts.length != 2) + set_exit_code(1, "Error: malformed argument: '" + currentArg); + runArgs.environment_variables[parts[0]] = parts[1]; + } else if (currentArg.startsWith("--runtime-arg=")) { + const arg = currentArg.substring("--runtime-arg=".length); + runArgs.runtimeArgs.push(arg); + } else if (currentArg == "--disable-on-demand-gc") { + runArgs.enableGC = false; + } else if (currentArg == "--diagnostic_tracing") { + runArgs.diagnosticTracing = true; + } else if (currentArg.startsWith("--working-dir=")) { + const arg = currentArg.substring("--working-dir=".length); + runArgs.workingDirectory = arg; + } else if (currentArg == "--debug") { + runArgs.debugging = true; + } else if (currentArg == "--no-forward-console") { + runArgs.forwardConsole = false; + } else if (currentArg.startsWith("--fetch-random-delay=")) { + const arg = currentArg.substring("--fetch-random-delay=".length); + if (is_browser) { + const delayms = Number.parseInt(arg) || 100; + const originalFetch = globalThis.fetch; + globalThis.fetch = async (url, options) => { + // random sleep + const ms = delayms + (Math.random() * delayms); + console.log(`fetch ${url} started ${ms}`) + await new Promise(resolve => setTimeout(resolve, ms)); + console.log(`fetch ${url} delayed ${ms}`) + const res = await originalFetch(url, options); + console.log(`fetch ${url} done ${ms}`) + return res; + } + } else { + console.warn("--fetch-random-delay only works on browser") + } + } else { + break; + } + incomingArguments = incomingArguments.slice(1); + } + + runArgs.applicationArguments = incomingArguments; + // cheap way to let the testing infrastructure know we're running in a browser context (or not) + runArgs.environment_variables["IsBrowserDomSupported"] = is_browser.toString().toLowerCase(); + runArgs.environment_variables["IsNodeJS"] = is_node.toString().toLowerCase(); + + return runArgs; +} + +function applyArguments() { + initRunArgs(); + + // set defaults + is_debugging = runArgs.debugging === true; + forward_console = runArgs.forwardConsole === true; + + console.log("Application arguments: " + runArgs.applicationArguments.join(' ')); + + if (forward_console) { + const methods = ["debug", "trace", "warn", "info", "error"]; + for (let m of methods) { + if (typeof (console[m]) !== "function") { + console[m] = proxyConsoleMethod(`console.${m}: `, console.log, false); + } + } + + if (is_browser) { + const consoleUrl = `${window.location.origin}/console`.replace('http://', 'ws://'); + + consoleWebSocket = new WebSocket(consoleUrl); + consoleWebSocket.onopen = function (event) { + originalConsole.log("browser: Console websocket connected."); + }; + consoleWebSocket.onerror = function (event) { + originalConsole.error(`websocket error: ${event}`, event); + }; + consoleWebSocket.onclose = function (event) { + originalConsole.error(`websocket closed: ${event}`, event); + }; + + const send = (msg) => { + if (consoleWebSocket.readyState === WebSocket.OPEN) { + consoleWebSocket.send(msg); + } + else { + originalConsole.log(msg); + } + } + + // redirect output early, so that when emscripten starts it's already redirected + for (let m of ["log", ...methods]) + console[m] = proxyConsoleMethod(`console.${m}`, send, true); + } + } +} + +async function loadDotnet(file) { + let loadScript = undefined; + if (typeof WScript !== "undefined") { // Chakra + loadScript = function (file) { + WScript.LoadScriptFile(file); + return globalThis.createDotnetRuntime; + }; + } else if (is_node) { // NodeJS + loadScript = async function (file) { + return require(file); + }; + } else if (is_browser) { // vanila JS in browser + loadScript = function (file) { + var loaded = new Promise((resolve, reject) => { + globalThis.__onDotnetRuntimeLoaded = (createDotnetRuntime) => { + // this is callback we have in CJS version of the runtime + resolve(createDotnetRuntime); + }; + import(file).then(({ default: createDotnetRuntime }) => { + // this would work with ES6 default export + if (createDotnetRuntime) { + resolve(createDotnetRuntime); + } + }, reject); + }); + return loaded; + } + } + else if (typeof globalThis.load !== 'undefined') { + loadScript = async function (file) { + globalThis.load(file) + return globalThis.createDotnetRuntime; + } + } + else { + throw new Error("Unknown environment, can't load config"); + } + + return loadScript(file); +} + +// this can't be function because of `arguments` scope +let queryArguments = []; +try { + if (is_node) { + queryArguments = process.argv.slice(2); + } else if (is_browser) { + // We expect to be run by tests/runtime/run.js which passes in the arguments using http parameters + const url = new URL(decodeURI(window.location)); + let urlArguments = [] + for (let param of url.searchParams) { + if (param[0] == "arg") { + urlArguments.push(param[1]); + } + } + queryArguments = urlArguments; + } else if (typeof arguments !== "undefined") { + queryArguments = Array.from(arguments); + } else if (typeof scriptArgs !== "undefined") { + queryArguments = Array.from(scriptArgs); + } else if (typeof WScript !== "undefined" && WScript.Arguments) { + queryArguments = Array.from(WScript.Arguments); + } +} catch (e) { + console.error(e); +} + +let loadDotnetPromise = loadDotnet('./dotnet.js'); +let argsPromise; + +if (queryArguments.length > 0) { + argsPromise = Promise.resolve(processQueryArguments(queryArguments)); +} else { + argsPromise = fetch('/runArgs.json') + .then(async (response) => { + if (!response.ok) { + console.debug(`could not load /args.json: ${response.status}. Ignoring`); + } else { + runArgs = await response.json(); + console.debug(`runArgs: ${JSON.stringify(runArgs)}`); + } + }) + .catch(error => console.error(`Failed to load args: ${stringify_as_error_with_stack(error)}`)); +} + if (typeof globalThis.crypto === 'undefined') { // **NOTE** this is a simple insecure polyfill for testing purposes only // /dev/random doesn't work on js shells, so define our own @@ -139,11 +358,57 @@ if (typeof globalThis.performance === 'undefined') { } } } -loadDotnet("./dotnet.js").then((createDotnetRuntime) => { + +let toAbsoluteUrl = function(possiblyRelativeUrl) { return possiblyRelativeUrl; } +if (is_browser) { + const anchorTagForAbsoluteUrlConversions = document.createElement('a'); + toAbsoluteUrl = function toAbsoluteUrl(possiblyRelativeUrl) { + anchorTagForAbsoluteUrlConversions.href = possiblyRelativeUrl; + return anchorTagForAbsoluteUrlConversions.href; + } +} + +Promise.all([ argsPromise, loadDotnetPromise ]).then(async ([ _, createDotnetRuntime ]) => { + applyArguments(); + + if (is_node) { + const modulesToLoad = runArgs.environment_variables["NPM_MODULES"]; + if (modulesToLoad) { + modulesToLoad.split(',').forEach(module => { + const { 0:moduleName, 1:globalAlias } = module.split(':'); + + let message = `Loading npm '${moduleName}'`; + let moduleExport = require(moduleName); + + if (globalAlias) { + message += ` and attaching to global as '${globalAlias}'`; + globalThis[globalAlias] = moduleExport; + } else if(moduleName == "node-fetch") { + message += ' and attaching to global'; + globalThis.fetch = moduleExport.default; + globalThis.Headers = moduleExport.Headers; + globalThis.Request = moduleExport.Request; + globalThis.Response = moduleExport.Response; + } else if(moduleName == "node-abort-controller") { + message += ' and attaching to global'; + globalThis.AbortController = moduleExport.AbortController; + } + + console.log(message); + }); + } + } + + // Must be after loading npm modules. + runArgs.environment_variables["IsWebSocketSupported"] = ("WebSocket" in globalThis).toString().toLowerCase(); + return createDotnetRuntime(({ MONO, INTERNAL, BINDING, Module }) => ({ disableDotnet6Compatibility: true, config: null, configSrc: "./mono-config.json", + locateFile: (path, prefix) => { + return toAbsoluteUrl(prefix + path); + }, onConfigLoaded: (config) => { if (!Module.config) { const err = new Error("Could not find ./mono-config.json. Cancelling run"); @@ -151,26 +416,32 @@ loadDotnet("./dotnet.js").then((createDotnetRuntime) => { throw err; } // Have to set env vars here to enable setting MONO_LOG_LEVEL etc. - for (let variable in processedArguments.setenv) { - config.environment_variables[variable] = processedArguments.setenv[variable]; + for (let variable in runArgs.environment_variables) { + config.environment_variables[variable] = runArgs.environment_variables[variable]; + } + config.diagnostic_tracing = !!runArgs.diagnosticTracing; + if (is_debugging) { + if (config.debug_level == 0) + config.debug_level = -1; + + config.wait_for_debugger = -1; } - config.diagnostic_tracing = !!processedArguments.diagnostic_tracing; }, preRun: () => { - if (!processedArguments.enable_gc) { + if (!runArgs.enableGC) { INTERNAL.mono_wasm_enable_on_demand_gc(0); } }, onDotnetReady: () => { - let wds = Module.FS.stat(processedArguments.working_dir); + let wds = Module.FS.stat(runArgs.workingDirectory); if (wds === undefined || !Module.FS.isDir(wds.mode)) { - set_exit_code(1, `Could not find working directory ${processedArguments.working_dir}`); + set_exit_code(1, `Could not find working directory ${runArgs.workingDirectory}`); return; } - Module.FS.chdir(processedArguments.working_dir); + Module.FS.chdir(runArgs.workingDirectory); - App.init({ MONO, INTERNAL, BINDING, Module }); + App.init({ MONO, INTERNAL, BINDING, Module, runArgs }); }, onAbort: (error) => { set_exit_code(1, stringify_as_error_with_stack(new Error())); @@ -181,26 +452,26 @@ loadDotnet("./dotnet.js").then((createDotnetRuntime) => { }); const App = { - init: async function ({ MONO, INTERNAL, BINDING, Module }) { + init: async function ({ MONO, INTERNAL, BINDING, Module, runArgs }) { + Object.assign(App, { MONO, INTERNAL, BINDING, Module, runArgs }); console.info("Initializing....."); - Object.assign(App, { MONO, INTERNAL, BINDING, Module }); - for (let i = 0; i < processedArguments.profilers.length; ++i) { - const init = Module.cwrap('mono_wasm_load_profiler_' + processedArguments.profilers[i], 'void', ['string']); + for (let i = 0; i < runArgs.profilers.length; ++i) { + const init = Module.cwrap('mono_wasm_load_profiler_' + runArgs.profilers[i], 'void', ['string']); init(""); } - if (processedArguments.applicationArgs.length == 0) { + if (runArgs.applicationArguments.length == 0) { set_exit_code(1, "Missing required --run argument"); return; } - if (processedArguments.applicationArgs[0] == "--regression") { + if (runArgs.applicationArguments[0] == "--regression") { const exec_regression = Module.cwrap('mono_wasm_exec_regression', 'number', ['number', 'string']); let res = 0; try { - res = exec_regression(10, processedArguments.applicationArgs[1]); + res = exec_regression(10, runArgs.applicationArguments[1]); console.log("REGRESSION RESULT: " + res); } catch (e) { console.error("ABORT: " + e); @@ -214,19 +485,19 @@ const App = { return; } - if (processedArguments.runtime_args.length > 0) - INTERNAL.mono_wasm_set_runtime_options(processedArguments.runtime_args); + if (runArgs.runtimeArgs.length > 0) + INTERNAL.mono_wasm_set_runtime_options(runArgs.runtimeArgs); - if (processedArguments.applicationArgs[0] == "--run") { + if (runArgs.applicationArguments[0] == "--run") { // Run an exe - if (processedArguments.applicationArgs.length == 1) { + if (runArgs.applicationArguments.length == 1) { set_exit_code(1, "Error: Missing main executable argument."); return; } try { - const main_assembly_name = processedArguments.applicationArgs[1]; - const app_args = processedArguments.applicationArgs.slice(2); - const result = await MONO.mono_run_main(main_assembly_name, app_args); + const main_assembly_name = runArgs.applicationArguments[1]; + const app_args = runArgs.applicationArguments.slice(2); + const result = await App.MONO.mono_run_main(main_assembly_name, app_args); set_exit_code(result); } catch (error) { if (error.name != "ExitStatus") { @@ -234,7 +505,7 @@ const App = { } } } else { - set_exit_code(1, "Unhandled argument: " + processedArguments.applicationArgs[0]); + set_exit_code(1, "Unhandled argument: " + runArgs.applicationArguments[0]); } }, @@ -257,209 +528,3 @@ const App = { }; globalThis.App = App; // Necessary as System.Runtime.InteropServices.JavaScript.Tests.MarshalTests (among others) call the App.call_test_method directly -function set_exit_code(exit_code, reason) { - if (reason) { - if (reason instanceof Error) - console.error(stringify_as_error_with_stack(reason)); - else if (typeof reason == "string") - console.error(reason); - else - console.error(JSON.stringify(reason)); - } - - if (is_browser) { - if (App.Module) { - // Notify the selenium script - App.Module.exit_code = exit_code; - } - - //Tell xharness WasmBrowserTestRunner what was the exit code - const tests_done_elem = document.createElement("label"); - tests_done_elem.id = "tests_done"; - tests_done_elem.innerHTML = exit_code.toString(); - document.body.appendChild(tests_done_elem); - - const stop_when_ws_buffer_empty = () => { - if (consoleWebSocket.bufferedAmount == 0) { - // tell xharness WasmTestMessagesProcessor we are done. - // note this sends last few bytes into the same WS - console.log("WASM EXIT " + exit_code); - } - else { - setTimeout(stop_when_ws_buffer_empty, 100); - } - }; - stop_when_ws_buffer_empty(); - - } else if (App && App.INTERNAL) { - App.INTERNAL.mono_wasm_exit(exit_code); - } -} - -function processArguments(incomingArguments) { - console.log("Incoming arguments: " + incomingArguments.join(' ')); - let profilers = []; - let setenv = {}; - let runtime_args = []; - let enable_gc = true; - let diagnostic_tracing = false; - let working_dir = '/'; - while (incomingArguments && incomingArguments.length > 0) { - const currentArg = incomingArguments[0]; - if (currentArg.startsWith("--profile=")) { - const arg = currentArg.substring("--profile=".length); - profilers.push(arg); - } else if (currentArg.startsWith("--setenv=")) { - const arg = currentArg.substring("--setenv=".length); - const parts = arg.split('='); - if (parts.length != 2) - set_exit_code(1, "Error: malformed argument: '" + currentArg); - setenv[parts[0]] = parts[1]; - } else if (currentArg.startsWith("--runtime-arg=")) { - const arg = currentArg.substring("--runtime-arg=".length); - runtime_args.push(arg); - } else if (currentArg == "--disable-on-demand-gc") { - enable_gc = false; - } else if (currentArg == "--diagnostic_tracing") { - diagnostic_tracing = true; - } else if (currentArg.startsWith("--working-dir=")) { - const arg = currentArg.substring("--working-dir=".length); - working_dir = arg; - } else if (currentArg.startsWith("--fetch-random-delay=")) { - const arg = currentArg.substring("--fetch-random-delay=".length); - if (is_browser) { - const delayms = Number.parseInt(arg) || 100; - const originalFetch = globalThis.fetch; - globalThis.fetch = async (url, options) => { - // random sleep - const ms = delayms + (Math.random() * delayms); - console.log(`fetch ${url} started ${ms}`) - await new Promise(resolve => setTimeout(resolve, ms)); - console.log(`fetch ${url} delayed ${ms}`) - const res = await originalFetch(url, options); - console.log(`fetch ${url} done ${ms}`) - return res; - } - } else { - console.warn("--fetch-random-delay only works on browser") - } - } else { - break; - } - incomingArguments = incomingArguments.slice(1); - } - - // cheap way to let the testing infrastructure know we're running in a browser context (or not) - setenv["IsBrowserDomSupported"] = is_browser.toString().toLowerCase(); - setenv["IsNodeJS"] = is_node.toString().toLowerCase(); - - console.log("Application arguments: " + incomingArguments.join(' ')); - - return { - applicationArgs: incomingArguments, - profilers, - setenv, - runtime_args, - enable_gc, - diagnostic_tracing, - working_dir, - } -} - -let processedArguments = null; -// this can't be function because of `arguments` scope -try { - if (is_node) { - processedArguments = processArguments(process.argv.slice(2)); - } else if (is_browser) { - // We expect to be run by tests/runtime/run.js which passes in the arguments using http parameters - const url = new URL(decodeURI(window.location)); - let urlArguments = [] - for (let param of url.searchParams) { - if (param[0] == "arg") { - urlArguments.push(param[1]); - } - } - processedArguments = processArguments(urlArguments); - } else if (typeof arguments !== "undefined") { - processedArguments = processArguments(Array.from(arguments)); - } else if (typeof scriptArgs !== "undefined") { - processedArguments = processArguments(Array.from(scriptArgs)); - } else if (typeof WScript !== "undefined" && WScript.Arguments) { - processedArguments = processArguments(Array.from(WScript.Arguments)); - } -} catch (e) { - console.error(e); -} - -if (is_node) { - const modulesToLoad = processedArguments.setenv["NPM_MODULES"]; - if (modulesToLoad) { - modulesToLoad.split(',').forEach(module => { - const { 0:moduleName, 1:globalAlias } = module.split(':'); - - let message = `Loading npm '${moduleName}'`; - let moduleExport = require(moduleName); - - if (globalAlias) { - message += ` and attaching to global as '${globalAlias}'`; - globalThis[globalAlias] = moduleExport; - } else if(moduleName == "node-fetch") { - message += ' and attaching to global'; - globalThis.fetch = moduleExport.default; - globalThis.Headers = moduleExport.Headers; - globalThis.Request = moduleExport.Request; - globalThis.Response = moduleExport.Response; - } else if(moduleName == "node-abort-controller") { - message += ' and attaching to global'; - globalThis.AbortController = moduleExport.AbortController; - } - - console.log(message); - }); - } -} - -// Must be after loading npm modules. -processedArguments.setenv["IsWebSocketSupported"] = ("WebSocket" in globalThis).toString().toLowerCase(); - -async function loadDotnet(file) { - let loadScript = undefined; - if (typeof WScript !== "undefined") { // Chakra - loadScript = function (file) { - WScript.LoadScriptFile(file); - return globalThis.createDotnetRuntime; - }; - } else if (is_node) { // NodeJS - loadScript = async function (file) { - return require(file); - }; - } else if (is_browser) { // vanila JS in browser - loadScript = function (file) { - var loaded = new Promise((resolve, reject) => { - globalThis.__onDotnetRuntimeLoaded = (createDotnetRuntime) => { - // this is callback we have in CJS version of the runtime - resolve(createDotnetRuntime); - }; - import(file).then(({ default: createDotnetRuntime }) => { - // this would work with ES6 default export - if (createDotnetRuntime) { - resolve(createDotnetRuntime); - } - }, reject); - }); - return loaded; - } - } - else if (typeof globalThis.load !== 'undefined') { - loadScript = async function (file) { - globalThis.load(file) - return globalThis.createDotnetRuntime; - } - } - else { - throw new Error("Unknown environment, can't load config"); - } - - return loadScript(file); -} diff --git a/src/tasks/WasmAppBuilder/JsonExtensions.cs b/src/tasks/WasmAppBuilder/JsonExtensions.cs new file mode 100644 index 00000000000000..a8a99b27be820a --- /dev/null +++ b/src/tasks/WasmAppBuilder/JsonExtensions.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Text.Json.Nodes; + +internal static class JsonExtensions +{ + public static T GetOrCreate(this JsonObject json, string name, Func creator) where T : JsonNode + { + if (json.TryGetPropertyValue(name, out JsonNode? node) && (node is T found)) + return found; + + JsonNode newObject = creator(); + if (newObject == null) + throw new ArgumentNullException($"BUG: got a null object for {name}"); + + json.Add(name, newObject); + return (T)newObject; + } +} diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index a25b404fc2ba86..5272edc5168dad 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.Json.Serialization; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -22,15 +23,14 @@ public class WasmAppBuilder : Task [Required] public string? MainJS { get; set; } - [NotNull] [Required] - public string[]? Assemblies { get; set; } + public string[] Assemblies { get; set; } = Array.Empty(); [NotNull] [Required] public ITaskItem[]? NativeAssets { get; set; } - private List _fileWrites = new(); + private readonly List _fileWrites = new(); [Output] public string[]? FileWrites => _fileWrites.ToArray(); @@ -45,6 +45,7 @@ public class WasmAppBuilder : Task public ITaskItem[]? RemoteSources { get; set; } public bool InvariantGlobalization { get; set; } public ITaskItem[]? ExtraFilesToDeploy { get; set; } + public string? MainHTMLPath { get; set; } // // Extra json elements to add to mono-config.json @@ -60,6 +61,16 @@ public class WasmAppBuilder : Task // public ITaskItem[]? ExtraConfig { get; set; } + public string? DefaultHostConfig { get; set; } + + [Required, NotNull] + public string? MainAssemblyName { get; set; } + + [Required] + public ITaskItem[] HostConfigs { get; set; } = Array.Empty(); + + public ITaskItem[] RuntimeArgsForHost { get; set; } = Array.Empty(); + private sealed class WasmAppConfig { [JsonPropertyName("assembly_root")] @@ -137,7 +148,7 @@ private bool ExecuteInternal () if (!InvariantGlobalization && string.IsNullOrEmpty(IcuDataFileName)) throw new LogAsErrorException("IcuDataFileName property shouldn't be empty if InvariantGlobalization=false"); - if (Assemblies?.Length == 0) + if (Assemblies.Length == 0) { Log.LogError("Cannot build Wasm app without any assemblies"); return false; @@ -149,6 +160,7 @@ private bool ExecuteInternal () if (!_assemblies.Contains(asm)) _assemblies.Add(asm); } + MainAssemblyName = Path.GetFileName(MainAssemblyName); var config = new WasmAppConfig (); @@ -179,10 +191,19 @@ private bool ExecuteInternal () FileCopyChecked(MainJS!, Path.Combine(AppDir, mainFileName), string.Empty); string indexHtmlPath = Path.Combine(AppDir, "index.html"); - if (!File.Exists(indexHtmlPath)) + if (string.IsNullOrEmpty(MainHTMLPath)) + { + if (!File.Exists(indexHtmlPath)) + { + var html = @""; + File.WriteAllText(indexHtmlPath, html); + } + } + else { - var html = @""; - File.WriteAllText(indexHtmlPath, html); + FileCopyChecked(MainHTMLPath, Path.Combine(AppDir, indexHtmlPath), "html"); + //var html = @""; + //File.WriteAllText(indexHtmlPath, html); } foreach (var assembly in _assemblies) @@ -319,9 +340,81 @@ private bool ExecuteInternal () } } + UpdateRuntimeConfigJson(); return !Log.HasLoggedErrors; } + private void UpdateRuntimeConfigJson() + { + string[] matchingAssemblies = Assemblies.Where(asm => Path.GetFileName(asm) == MainAssemblyName).ToArray(); + if (matchingAssemblies.Length == 0) + throw new LogAsErrorException($"Could not find main assembly named {MainAssemblyName} in the list of assemblies"); + + if (matchingAssemblies.Length > 1) + throw new LogAsErrorException($"Found more than one assembly matching the main assembly name {MainAssemblyName}: {string.Join(",", matchingAssemblies)}"); + + string runtimeConfigPath = Path.ChangeExtension(matchingAssemblies[0], ".runtimeconfig.json"); + if (!File.Exists(runtimeConfigPath)) + { + Log.LogMessage(MessageImportance.Low, $"Could not find {runtimeConfigPath}. Ignoring."); + return; + } + + var rootNode = JsonNode.Parse(File.ReadAllText(runtimeConfigPath), + new JsonNodeOptions { PropertyNameCaseInsensitive = true }); + if (rootNode == null) + throw new LogAsErrorException($"Failed to parse {runtimeConfigPath}"); + + JsonObject? rootObject = rootNode.AsObject(); + if (!rootObject.TryGetPropertyValue("runtimeOptions", out JsonNode? runtimeOptionsNode) + || !(runtimeOptionsNode is JsonObject runtimeOptionsObject)) + { + throw new LogAsErrorException($"Could not find node named 'runtimeOptions' in {runtimeConfigPath}"); + } + + JsonObject wasmHostProperties = runtimeOptionsObject.GetOrCreate("wasmHostProperties", () => new JsonObject()); + JsonArray runtimeArgsArray = wasmHostProperties.GetOrCreate("runtimeArgs", () => new JsonArray()); + JsonArray perHostConfigs = wasmHostProperties.GetOrCreate("perHostConfig", () => new JsonArray()); + + if (string.IsNullOrEmpty(DefaultHostConfig) && HostConfigs.Length > 0) + DefaultHostConfig = HostConfigs[0].ItemSpec; + + if (!string.IsNullOrEmpty(DefaultHostConfig)) + wasmHostProperties["defaultConfig"] = DefaultHostConfig; + + wasmHostProperties["mainAssembly"] = MainAssemblyName; + + foreach (JsonValue? rarg in RuntimeArgsForHost.Select(ri => JsonValue.Create(ri.ItemSpec))) + { + if (rarg is not null) + runtimeArgsArray.Add(rarg); + } + + foreach (ITaskItem hostConfigItem in HostConfigs) + { + var hostConfigObject = new JsonObject(); + + string name = hostConfigItem.ItemSpec; + string host = hostConfigItem.GetMetadata("host"); + if (string.IsNullOrEmpty(host)) + throw new LogAsErrorException($"BUG: Could not find required metadata 'host' for host config named '{name}'"); + + hostConfigObject.Add("name", name); + foreach (KeyValuePair kvp in hostConfigItem.CloneCustomMetadata().Cast>()) + hostConfigObject.Add(kvp.Key, kvp.Value); + + perHostConfigs.Add(hostConfigObject); + } + + string dstPath = Path.Combine(AppDir!, Path.GetFileName(runtimeConfigPath)); + using FileStream? fs = File.OpenWrite(dstPath); + using var writer = new Utf8JsonWriter(fs, new JsonWriterOptions { Indented = true }); + rootObject.WriteTo(writer); + _fileWrites.Add(dstPath); + + Log.LogMessage(MessageImportance.Low, $"Generated {dstPath} from {runtimeConfigPath}"); + } + private bool TryParseExtraConfigValue(ITaskItem extraItem, out object? valueObject) { valueObject = null; @@ -361,7 +454,7 @@ private static bool TryConvert(string str, Type type, out object? value) value = Convert.ChangeType(str, type); return true; } - catch (Exception ex) when (ex is FormatException || ex is InvalidCastException || ex is OverflowException) + catch (Exception ex) when (ex is FormatException or InvalidCastException or OverflowException) { return false; } diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index 44dec10c67b23a..7c8465c6bc0b4e 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -21,6 +21,7 @@ + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BrowserRunner.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BrowserRunner.cs new file mode 100644 index 00000000000000..b80ab482075bce --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BrowserRunner.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.IO; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Wasm.Tests.Internal; + +namespace Wasm.Build.Tests; + +internal class BrowserRunner : IAsyncDisposable +{ + private static Regex s_blazorUrlRegex = new Regex("Now listening on: (?https?://.*$)"); + private static Regex s_appHostUrlRegex = new Regex("^App url: (?https?://.*$)"); + private static Regex s_exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); + private static readonly Lazy s_chromePath = new(() => + { + string artifactsBinDir = Path.Combine(Path.GetDirectoryName(typeof(BuildTestBase).Assembly.Location)!, "..", "..", "..", ".."); + return BrowserLocator.FindChrome(artifactsBinDir, "BROWSER_PATH_FOR_TESTS"); + }); + + public IPlaywright? Playwright { get; private set; } + public IBrowser? Browser { get; private set; } + public Task? RunTask { get; private set; } + public IList OutputLines { get; private set; } = new List(); + private TaskCompletionSource _exited = new(); + + // FIXME: options + public async Task RunAsync(ToolCommand cmd, string args, bool headless = true) + { + TaskCompletionSource urlAvailable = new(); + Action outputHandler = msg => + { + if (string.IsNullOrEmpty(msg)) + return; + + OutputLines.Add(msg); + + Match m = s_appHostUrlRegex.Match(msg); + if (!m.Success) + m = s_blazorUrlRegex.Match(msg); + + if (m.Success) + { + string url = m.Groups["url"].Value; + if (url.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) + urlAvailable.TrySetResult(m.Groups["url"].Value); + return; + } + + m = s_exitRegex.Match(msg); + if (m.Success) + { + _exited.SetResult(int.Parse(m.Groups["exitCode"].Value)); + return; + } + }; + + cmd.WithErrorDataReceived(outputHandler).WithOutputDataReceived(outputHandler); + var runTask = cmd.ExecuteAsync(args); + + await Task.WhenAny(runTask, urlAvailable.Task, Task.Delay(TimeSpan.FromSeconds(30))); + if (runTask.IsCompleted) + { + var res = await runTask; + res.EnsureSuccessful(); + + throw new Exception($"Process ended before the url was found"); + } + if (!urlAvailable.Task.IsCompleted) + throw new Exception("Timed out waiting for the app host url"); + + Playwright = await Microsoft.Playwright.Playwright.CreateAsync(); + Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions{ + ExecutablePath = s_chromePath.Value, + Headless = headless + }); + + IPage page = await Browser.NewPageAsync(); + await page.GotoAsync(urlAvailable.Task.Result); + RunTask = runTask; + return page; + } + + public async Task WaitForExitMessageAsync(TimeSpan timeout) + { + if (RunTask is null || RunTask.IsCompleted) + throw new Exception($"No run task, or already completed"); + + await Task.WhenAny(RunTask!, _exited.Task, Task.Delay(timeout)); + if (_exited.Task.IsCompleted) + { + Console.WriteLine ($"Exited with {await _exited.Task}"); + return; + } + + throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for 'WASM EXIT' message"); + } + + public async Task WaitForProcessExitAsync(TimeSpan timeout) + { + if (RunTask is null || RunTask.IsCompleted) + throw new Exception($"No run task, or already completed"); + + await Task.WhenAny(RunTask!, _exited.Task, Task.Delay(timeout)); + if (RunTask.IsCanceled) + { + Console.WriteLine ($"Exited with {(await RunTask).ExitCode}"); + return; + } + + throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); + } + + public async ValueTask DisposeAsync() + { + if (Browser is not null) + await Browser.DisposeAsync(); + Playwright?.Dispose(); + } +} diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/RunCommand.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/RunCommand.cs new file mode 100644 index 00000000000000..01f1e1efacd21c --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/RunCommand.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using Xunit.Abstractions; + +namespace Wasm.Build.Tests; + +public class RunCommand : DotNetCommand +{ + public RunCommand(BuildEnvironment buildEnv, ITestOutputHelper _testOutput, string label="") : base(buildEnv, _testOutput, false, label) + { + WithEnvironmentVariable("DOTNET_ROOT", buildEnv.DotNet); + WithEnvironmentVariable("DOTNET_INSTALL_DIR", Path.GetDirectoryName(buildEnv.DotNet)!); + WithEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0"); + WithEnvironmentVariable("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "1"); + } +} diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj index c8e810f68be384..155bf17d3486fe 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -13,11 +13,15 @@ true true + true false + true + + RunScriptTemplate.cmd RunScriptTemplate.sh @@ -26,6 +30,10 @@ + + + + @@ -41,6 +49,9 @@ + + + diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs index 0624a488667f97..2ba538336c467a 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/WasmTemplateTests.cs @@ -85,12 +85,15 @@ public void ConsoleBuildThenPublish(string config) TargetFramework: "net7.0" )); + (int exitCode, string output) = RunProcess(s_buildEnv.DotNet, _testOutput, args: $"run --no-build -c {config}", workingDir: _projectDir); + Assert.Equal(0, exitCode); + Assert.Contains("Hello, Console!", output); + if (!_buildContext.TryGetBuildFor(buildArgs, out BuildProduct? product)) throw new XunitException($"Test bug: could not get the build product in the cache"); File.Move(product!.LogFile, Path.ChangeExtension(product.LogFile!, ".first.binlog")); - _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}"); _testOutput.WriteLine($"{Environment.NewLine}Publishing with no changes ..{Environment.NewLine}"); bool expectRelinking = config == "Release"; @@ -105,5 +108,153 @@ public void ConsoleBuildThenPublish(string config) TargetFramework: "net7.0", UseCache: false)); } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug")] + [InlineData("Release")] + public void ConsoleBuildAndRun(string config) + { + string id = $"{config}_{Path.GetRandomFileName()}"; + string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); + string projectName = Path.GetFileNameWithoutExtension(projectFile); + + string programText = """ + using System; + + for (int i = 0; i < args.Length; i ++) + Console.WriteLine ($"args[{i}] = {args[i]}"); + """; + File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); + + var buildArgs = new BuildArgs(projectName, config, false, id, null); + buildArgs = ExpandBuildArgs(buildArgs); + + BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + DotnetWasmFromRuntimePack: true, + CreateProject: false, + HasV8Script: false, + MainJS: "main.cjs", + Publish: false, + TargetFramework: "net7.0" + )); + + (int exitCode, string output) = RunProcess(s_buildEnv.DotNet, _testOutput, args: $"run --no-build -c {config} x y z", workingDir: _projectDir); + Assert.Equal(0, exitCode); + Assert.Contains("args[0] = x", output); + Assert.Contains("args[1] = y", output); + Assert.Contains("args[2] = z", output); + } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug", false)] + [InlineData("Debug", true)] + [InlineData("Release", false)] + [InlineData("Release", true)] + public void ConsolePublishAndRun(string config, bool aot) + { + string id = $"{config}_{Path.GetRandomFileName()}"; + string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); + string projectName = Path.GetFileNameWithoutExtension(projectFile); + + string programText = """ + using System; + + for (int i = 0; i < args.Length; i ++) + Console.WriteLine ($"args[{i}] = {args[i]}"); + """; + File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); + if (aot) + AddItemsPropertiesToProject(projectFile, "true"); + + var buildArgs = new BuildArgs(projectName, config, aot, id, null); + buildArgs = ExpandBuildArgs(buildArgs); + + bool expectRelinking = config == "Release" || aot; + BuildProject(buildArgs, + id: id, + new BuildProjectOptions( + DotnetWasmFromRuntimePack: !expectRelinking, + CreateProject: false, + HasV8Script: false, + MainJS: "main.cjs", + Publish: true, + TargetFramework: "net7.0", + UseCache: false)); + + // FIXME: pass envvars via the environment, once that is supported + string runArgs = $"run --no-build -c {config}"; + if (aot) + runArgs += $" --setenv=MONO_LOG_MASK=aot --setenv=MONO_LOG_LEVEL=debug"; + runArgs += " x y z"; + var res = new RunCommand(s_buildEnv, _testOutput, label: id) + .WithWorkingDirectory(_projectDir!) + .ExecuteWithCapturedOutput(runArgs) + .EnsureSuccessful(); + + if (aot) + Assert.Contains($"AOT: image '{Path.GetFileNameWithoutExtension(projectFile)}' found", res.Output); + Assert.Contains("args[0] = x", res.Output); + Assert.Contains("args[1] = y", res.Output); + Assert.Contains("args[2] = z", res.Output); + } + +#if false // FIXME: playwright on CI + [ConditionalFact(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + public async Task BlazorRunTest() + { + string config = "Debug"; + string id = $"blazor_{config}_{Path.GetRandomFileName()}"; + string projectFile = CreateWasmTemplateProject(id, "blazorwasm"); + // string projectName = Path.GetFileNameWithoutExtension(projectFile); + + // var buildArgs = new BuildArgs(projectName, config, false, id, null); + // buildArgs = ExpandBuildArgs(buildArgs); + + new DotNetCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!) + .Execute($"build -c {config} -bl:{Path.Combine(s_buildEnv.LogRootPath, $"{id}.binlog")}") + .EnsureSuccessful(); + + using var runCommand = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!); + + await using var runner = new BrowserRunner(); + var page = await runner.RunAsync(runCommand, $"run -c {config} --no-build"); + + await page.Locator("text=Counter").ClickAsync(); + var txt = await page.Locator("p[role='status']").InnerHTMLAsync(); + Assert.Equal("Current count: 0", txt); + + await page.Locator("text=\"Click me\"").ClickAsync(); + txt = await page.Locator("p[role='status']").InnerHTMLAsync(); + Assert.Equal("Current count: 1", txt); + } + + [ConditionalFact(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + public async Task BrowserTest() + { + string config = "Debug"; + string id = $"browser_{config}_{Path.GetRandomFileName()}"; + CreateWasmTemplateProject(id, "wasmbrowser"); + + // var buildArgs = new BuildArgs(projectName, config, false, id, null); + // buildArgs = ExpandBuildArgs(buildArgs); + + new DotNetCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!) + .Execute($"build -c {config} -bl:{Path.Combine(s_buildEnv.LogRootPath, $"{id}.binlog")}") + .EnsureSuccessful(); + + using var runCommand = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!); + + await using var runner = new BrowserRunner(); + var page = await runner.RunAsync(runCommand, $"run -c {config} --no-build -r browser-wasm --forward-console"); + await runner.WaitForExitMessageAsync(TimeSpan.FromMinutes(2)); + Assert.Contains("Hello, Browser!", string.Join(Environment.NewLine, runner.OutputLines)); + } +#endif } } From 1985e6557896c6b565f308a5c7735464c9c43973 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 18 May 2022 00:15:13 +0000 Subject: [PATCH 2/5] cleanup --- app-support.js | 451 --------------------- src/mono/wasm/host/CommandLineException.cs | 15 + src/mono/wasm/host/CommonConfiguration.cs | 23 +- src/mono/wasm/host/JSEngineHost.cs | 4 +- src/mono/wasm/host/Program.cs | 7 +- 5 files changed, 37 insertions(+), 463 deletions(-) delete mode 100644 app-support.js create mode 100644 src/mono/wasm/host/CommandLineException.cs diff --git a/app-support.js b/app-support.js deleted file mode 100644 index e55232ec706949..00000000000000 --- a/app-support.js +++ /dev/null @@ -1,451 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// -*- mode: js; js-indent-level: 4; -*- -// -"use strict"; - -//glue code to deal with the differences between chrome, ch, d8, jsc and sm. -const is_browser = typeof window != "undefined"; -const is_node = !is_browser && typeof process === 'object' && typeof process.versions === 'object' && typeof process.versions.node === 'string'; -export const App = {}; - -if (is_node && process.versions.node.split(".")[0] < 14) { - throw new Error(`NodeJS at '${process.execPath}' has too low version '${process.versions.node}'`); -} - -// if the engine doesn't provide a console -if (typeof (console) === "undefined") { - globalThis.console = { - log: globalThis.print, - clear: function () { } - }; -} -const originalConsole = { - log: console.log, - error: console.error -}; - -let consoleWebSocket; -let processedArguments = null; -let is_debugging = false; -// FIXME: process args before all this -let forward_console = true; - -function processArguments(incomingArguments) { - console.log("Incoming arguments: " + incomingArguments.join(' ')); - let profilers = []; - let setenv = {}; - let runtime_args = []; - let enable_gc = true; - let diagnostic_tracing = false; - let working_dir = '/'; - while (incomingArguments && incomingArguments.length > 0) { - const currentArg = incomingArguments[0]; - if (currentArg.startsWith("--profile=")) { - const arg = currentArg.substring("--profile=".length); - profilers.push(arg); - } else if (currentArg.startsWith("--setenv=")) { - const arg = currentArg.substring("--setenv=".length); - const parts = arg.split('='); - if (parts.length != 2) - set_exit_code(1, "Error: malformed argument: '" + currentArg); - setenv[parts[0]] = parts[1]; - } else if (currentArg.startsWith("--runtime-arg=")) { - const arg = currentArg.substring("--runtime-arg=".length); - runtime_args.push(arg); - } else if (currentArg == "--disable-on-demand-gc") { - enable_gc = false; - } else if (currentArg == "--diagnostic_tracing") { - diagnostic_tracing = true; - } else if (currentArg.startsWith("--working-dir=")) { - const arg = currentArg.substring("--working-dir=".length); - working_dir = arg; - } else if (currentArg == "--debug") { - is_debugging = true; - } else if (currentArg == "--no-forward-console") { - forward_console = false; - } else { - break; - } - incomingArguments = incomingArguments.slice(1); - } - - // cheap way to let the testing infrastructure know we're running in a browser context (or not) - setenv["IsBrowserDomSupported"] = is_browser.toString().toLowerCase(); - setenv["IsNodeJS"] = is_node.toString().toLowerCase(); - - console.log("Application arguments: " + incomingArguments.join(' ')); - - return { - applicationArgs: incomingArguments, - profilers, - setenv, - runtime_args, - enable_gc, - diagnostic_tracing, - working_dir, - } -} - -function proxyConsoleMethod(prefix, func, asJson) { - return function () { - try { - const args = [...arguments]; - let payload = args[0]; - if (payload === undefined) payload = 'undefined'; - else if (payload === null) payload = 'null'; - else if (typeof payload === 'function') payload = payload.toString(); - else if (typeof payload !== 'string') { - try { - payload = JSON.stringify(payload); - } catch (e) { - payload = payload.toString(); - } - } - - if (payload.startsWith("STARTRESULTXML")) { - originalConsole.log('Sending RESULTXML') - func(payload); - } - else if (asJson) { - func(JSON.stringify({ - method: prefix, - payload: payload, - arguments: args - })); - } else { - func([prefix + payload, ...args.slice(1)]); - } - } catch (err) { - originalConsole.error(`proxyConsole failed: ${err}`) - } - }; -}; - -// this can't be function because of `arguments` scope -try { - if (is_node) { - processedArguments = processArguments(process.argv.slice(2)); - } else if (is_browser) { - // We expect to be run by tests/runtime/run.js which passes in the arguments using http parameters - const url = new URL(decodeURI(window.location)); - let urlArguments = [] - for (let param of url.searchParams) { - if (param[0] == "arg") { - urlArguments.push(param[1]); - } - } - processedArguments = processArguments(urlArguments); - } else if (typeof arguments !== "undefined") { - processedArguments = processArguments(Array.from(arguments)); - } else if (typeof scriptArgs !== "undefined") { - processedArguments = processArguments(Array.from(scriptArgs)); - } else if (typeof WScript !== "undefined" && WScript.Arguments) { - processedArguments = processArguments(Array.from(WScript.Arguments)); - } -} catch (e) { - console.error(e); -} - -if (is_node) { - const modulesToLoad = processedArguments.setenv["NPM_MODULES"]; - if (modulesToLoad) { - modulesToLoad.split(',').forEach(module => { - const { 0:moduleName, 1:globalAlias } = module.split(':'); - - let message = `Loading npm '${moduleName}'`; - let moduleExport = require(moduleName); - - if (globalAlias) { - message += ` and attaching to global as '${globalAlias}'`; - globalThis[globalAlias] = moduleExport; - } else if(moduleName == "node-fetch") { - message += ' and attaching to global'; - globalThis.fetch = moduleExport.default; - globalThis.Headers = moduleExport.Headers; - globalThis.Request = moduleExport.Request; - globalThis.Response = moduleExport.Response; - } else if(moduleName == "node-abort-controller") { - message += ' and attaching to global'; - globalThis.AbortController = moduleExport.AbortController; - } - - console.log(message); - }); - } -} - -if (forward_console) { - const methods = ["debug", "trace", "warn", "info", "error"]; - for (let m of methods) { - if (typeof (console[m]) !== "function") { - console[m] = proxyConsoleMethod(`console.${m}: `, console.log, false); - } - } - - if (is_browser) { - const consoleUrl = `${window.location.origin}/console`.replace('http://', 'ws://'); - - consoleWebSocket = new WebSocket(consoleUrl); - consoleWebSocket.onopen = function (event) { - originalConsole.log("browser: Console websocket connected."); - }; - consoleWebSocket.onerror = function (event) { - originalConsole.error(`websocket error: ${event}`); - }; - consoleWebSocket.onclose = function (event) { - originalConsole.error(`websocket closed: ${event}`); - }; - - const send = (msg) => { - if (consoleWebSocket.readyState === WebSocket.OPEN) { - consoleWebSocket.send(msg); - } - else { - originalConsole.log(msg); - } - } - - // redirect output early, so that when emscripten starts it's already redirected - for (let m of ["log", ...methods]) - console[m] = proxyConsoleMethod(`console.${m}`, send, true); - } -} - -function stringify_as_error_with_stack(err) { - if (!err) - return ""; - - // FIXME: - if (App && App.INTERNAL) - return App.INTERNAL.mono_wasm_stringify_as_error_with_stack(err); - - if (err.stack) - return err.stack; - - if (typeof err == "string") - return err; - - return JSON.stringify(err); -} - -if (typeof globalThis.crypto === 'undefined') { - // **NOTE** this is a simple insecure polyfill for testing purposes only - // /dev/random doesn't work on js shells, so define our own - // See library_fs.js:createDefaultDevices () - globalThis.crypto = { - getRandomValues: function (buffer) { - for (let i = 0; i < buffer.length; i++) - buffer[i] = (Math.random() * 256) | 0; - } - } -} - -if (typeof globalThis.performance === 'undefined') { - if (is_node) { - const { performance } = require("perf_hooks"); - globalThis.performance = performance; - } else { - // performance.now() is used by emscripten and doesn't work in JSC - globalThis.performance = { - now: function () { - return Date.now(); - } - } - } -} - -let toAbsoluteUrl = function(possiblyRelativeUrl) { return possiblyRelativeUrl; } -if (is_browser) { - const anchorTagForAbsoluteUrlConversions = document.createElement('a'); - toAbsoluteUrl = function toAbsoluteUrl(possiblyRelativeUrl) { - anchorTagForAbsoluteUrlConversions.href = possiblyRelativeUrl; - return anchorTagForAbsoluteUrlConversions.href; - } -} - -if (is_node) { - module.exports.App = App; - module.exports.is_browser = is_browser; - module.exports.is_node = is_node; - module.exports.set_exit_code = set_exit_code; -} - -// Must be after loading npm modules. -processedArguments.setenv["IsWebSocketSupported"] = ("WebSocket" in globalThis).toString().toLowerCase(); - -loadDotnet("./dotnet.js").then((createDotnetRuntime) => { - return createDotnetRuntime(({ MONO, INTERNAL, BINDING, Module }) => ({ - disableDotnet6Compatibility: true, - config: null, - configSrc: "./mono-config.json", - locateFile: (path, prefix) => { - return toAbsoluteUrl(prefix + path); - }, - onConfigLoaded: (config) => { - if (!Module.config) { - const err = new Error("Could not find ./mono-config.json. Cancelling run"); - set_exit_code(1); - throw err; - } - // Have to set env vars here to enable setting MONO_LOG_LEVEL etc. - for (let variable in processedArguments.setenv) { - config.environment_variables[variable] = processedArguments.setenv[variable]; - } - config.diagnostic_tracing = !!processedArguments.diagnostic_tracing; - if (is_debugging && config.debug_level == 0) - config.debug_level = -1; - }, - preRun: () => { - if (!processedArguments.enable_gc) { - INTERNAL.mono_wasm_enable_on_demand_gc(0); - } - }, - onDotnetReady: () => { - let wds = Module.FS.stat(processedArguments.working_dir); - if (wds === undefined || !Module.FS.isDir(wds.mode)) { - set_exit_code(1, `Could not find working directory ${processedArguments.working_dir}`); - return; - } - - Module.FS.chdir(processedArguments.working_dir); - - if (processedArguments.runtime_args.length > 0) - INTERNAL.mono_wasm_set_runtime_options(processedArguments.runtime_args); - - console.info("Initializing....."); - Object.assign(App, { MONO, INTERNAL, BINDING, Module, processedArguments }); - - try { - if (App.init) - { - let ret = App.init(); - Promise.resolve(ret).then(function (code) { set_exit_code(code ?? 0); }); - } - else - { - console.log("WASM ERROR: no App.init defined"); - set_exit_code(1, "WASM ERROR: no App.init defined"); - } - } catch (err) { - console.log(`WASM ERROR ${err}`); - if (is_browser && document.getElementById("out")) - document.getElementById("out").innerHTML = `error: ${err}`; - set_exit_code(1, err); - } - }, - onAbort: (error) => { - set_exit_code(1, stringify_as_error_with_stack(new Error())); - }, - })) -}).catch(function (err) { - set_exit_code(1, "failed to load the dotnet.js file.\n" + stringify_as_error_with_stack(err)); -}); - -function set_exit_code(exit_code, reason) { - if (reason) { - if (reason instanceof Error) - console.error(stringify_as_error_with_stack(reason)); - else if (typeof reason == "string") - console.error(reason); - else - console.error(JSON.stringify(reason)); - } - - if (is_browser) { - if (App.Module) { - // Notify the selenium script - App.Module.exit_code = exit_code; - } - - //Tell xharness WasmBrowserTestRunner what was the exit code - const tests_done_elem = document.createElement("label"); - tests_done_elem.id = "tests_done"; - tests_done_elem.innerHTML = exit_code.toString(); - document.body.appendChild(tests_done_elem); - - if (forward_console) { - const stop_when_ws_buffer_empty = () => { - if (consoleWebSocket.bufferedAmount == 0) { - // tell xharness WasmTestMessagesProcessor we are done. - // note this sends last few bytes into the same WS - console.log("WASM EXIT " + exit_code); - } - else { - setTimeout(stop_when_ws_buffer_empty, 100); - } - }; - stop_when_ws_buffer_empty(); - } else { - console.log("WASM EXIT " + exit_code); - } - - } else if (App && App.INTERNAL) { - App.INTERNAL.mono_wasm_exit(exit_code); - } -} - -let loadScript2 = undefined; -if (typeof WScript !== "undefined") { // Chakra - loadScript2 = function (file) { - return Promise.resolve(WScript.LoadScriptFile(file)); - }; -} else if (is_node) { // NodeJS - loadScript2 = function (file) { - return Promise.resolve(require(file)); - }; -} else if (is_browser) { // vanila JS in browser - loadScript2 = function (file) { - return Promise.resolve(import(file)); - } -} -else if (typeof globalThis.load !== 'undefined') { - loadScript2 = function (file) { - return Promise.resolve(globalThis.load(file)); - } -} -else { - throw new Error("Unknown environment, can't figure out a `loadScript2` to use"); -} -globalThis.loadScript2 = loadScript2; - -async function loadDotnet(file) { - let loadScript = undefined; - if (typeof WScript !== "undefined") { // Chakra - loadScript = function (file) { - WScript.LoadScriptFile(file); - return globalThis.createDotnetRuntime; - }; - } else if (is_node) { // NodeJS - loadScript = async function (file) { - return require(file); - }; - } else if (is_browser) { // vanila JS in browser - loadScript = function (file) { - var loaded = new Promise((resolve, reject) => { - globalThis.__onDotnetRuntimeLoaded = (createDotnetRuntime) => { - // this is callback we have in CJS version of the runtime - resolve(createDotnetRuntime); - }; - import(file).then(({ default: createDotnetRuntime }) => { - // this would work with ES6 default export - if (createDotnetRuntime) { - resolve(createDotnetRuntime); - } - }, reject); - }); - return loaded; - } - } - else if (typeof globalThis.load !== 'undefined') { - loadScript = async function (file) { - globalThis.load(file) - return globalThis.createDotnetRuntime; - } - } - else { - throw new Error("Unknown environment, can't load config"); - } - - return loadScript(file); -} diff --git a/src/mono/wasm/host/CommandLineException.cs b/src/mono/wasm/host/CommandLineException.cs new file mode 100644 index 00000000000000..4a2e27f2332afe --- /dev/null +++ b/src/mono/wasm/host/CommandLineException.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; + +namespace Microsoft.WebAssembly.AppHost; + +public class CommandLineException : Exception +{ + public CommandLineException() { } + public CommandLineException(string message) : base(message) { } + public CommandLineException(string message, Exception inner) : base(message, inner) { } +} diff --git a/src/mono/wasm/host/CommonConfiguration.cs b/src/mono/wasm/host/CommonConfiguration.cs index 427acff3dc34c8..309800ae1f6c4c 100644 --- a/src/mono/wasm/host/CommonConfiguration.cs +++ b/src/mono/wasm/host/CommonConfiguration.cs @@ -42,10 +42,10 @@ private CommonConfiguration(string[] args) { string[] configs = Directory.EnumerateFiles(Environment.CurrentDirectory, "*.runtimeconfig.json").ToArray(); if (configs.Length == 0) - throw new Exception($"Could not find any runtimeconfig.json in {Environment.CurrentDirectory}. Use --runtime-config= to specify the path"); + throw new CommandLineException($"Could not find any runtimeconfig.json in {Environment.CurrentDirectory}. Use --runtime-config= to specify the path"); if (configs.Length > 1) - throw new Exception($"Found multiple runtimeconfig.json files: {string.Join(", ", configs)}. Use --runtime-config= to specify one"); + throw new CommandLineException($"Found multiple runtimeconfig.json files: {string.Join(", ", configs)}. Use --runtime-config= to specify one"); _runtimeConfigPath = Path.GetFullPath(configs[0]); } @@ -53,7 +53,7 @@ private CommonConfiguration(string[] args) AppPath = Path.GetDirectoryName(_runtimeConfigPath) ?? "."; if (string.IsNullOrEmpty(_runtimeConfigPath) || !File.Exists(_runtimeConfigPath)) - throw new Exception($"Cannot find runtime config at {_runtimeConfigPath}"); + throw new CommandLineException($"Cannot find runtime config at {_runtimeConfigPath}"); RuntimeConfig? rconfig = JsonSerializer.Deserialize( File.ReadAllText(_runtimeConfigPath), @@ -64,31 +64,36 @@ private CommonConfiguration(string[] args) PropertyNameCaseInsensitive = true }); if (rconfig == null) - throw new Exception($"Failed to deserialize {_runtimeConfigPath}"); + throw new CommandLineException($"Failed to deserialize {_runtimeConfigPath}"); if (rconfig.RuntimeOptions == null) - throw new Exception($"Failed to deserialize {_runtimeConfigPath} - rconfig.RuntimeOptions"); + throw new CommandLineException($"Failed to deserialize {_runtimeConfigPath} - rconfig.RuntimeOptions"); HostProperties = rconfig.RuntimeOptions.WasmHostProperties; if (HostProperties == null) - throw new Exception($"Failed to deserialize {_runtimeConfigPath} - config"); + throw new CommandLineException($"Failed to deserialize {_runtimeConfigPath} - config"); if (HostProperties.HostConfigs is null || HostProperties.HostConfigs.Count == 0) - throw new Exception($"no perHostConfigs found"); + throw new CommandLineException($"no perHostConfigs found"); // read only if it wasn't overridden by command line option string desiredConfig = hostArg ?? HostProperties.DefaultConfig; HostConfig? foundConfig = HostProperties.HostConfigs .Where(hc => string.Equals(hc.Name, desiredConfig, StringComparison.InvariantCultureIgnoreCase)) .FirstOrDefault(); + if (foundConfig is null && !string.IsNullOrEmpty(hostArg)) + { + string validHosts = string.Join(", ", HostProperties.HostConfigs.Select(hc => hc.Name)); + throw new CommandLineException($"Unknown host '{hostArg}'. Valid options: {validHosts}"); + } HostConfig = foundConfig ?? HostProperties.HostConfigs.First(); if (HostConfig == null) - throw new Exception("no host config found"); + throw new CommandLineException("no host config found"); // FIXME: validate hostconfig if (!Enum.TryParse(HostConfig.HostString, ignoreCase: true, out WasmHost wasmHost)) - throw new Exception($"Unknown host {HostConfig.HostString} in config named {HostConfig.Name}"); + throw new CommandLineException($"Unknown host {HostConfig.HostString} in config named {HostConfig.Name}"); Host = wasmHost; } diff --git a/src/mono/wasm/host/JSEngineHost.cs b/src/mono/wasm/host/JSEngineHost.cs index 9ec8d70abc6de9..2297603f252913 100644 --- a/src/mono/wasm/host/JSEngineHost.cs +++ b/src/mono/wasm/host/JSEngineHost.cs @@ -45,7 +45,7 @@ private async Task RunAsync() WasmHost.JavaScriptCore => "jsc", WasmHost.SpiderMonkey => "sm", WasmHost.NodeJS => "node", - _ => throw new ArgumentException($"Unsupported engine {_args.Host}") + _ => throw new CommandLineException($"Unsupported engine {_args.Host}") }; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -57,7 +57,7 @@ private async Task RunAsync() } if (_args.CommonConfig.Debugging) - throw new Exception($"Debugging not supported with {_args.Host}"); + throw new CommandLineException($"Debugging not supported with {_args.Host}"); var args = new List(); diff --git a/src/mono/wasm/host/Program.cs b/src/mono/wasm/host/Program.cs index 6441afa8a2727f..90e0d7bc0830fa 100644 --- a/src/mono/wasm/host/Program.cs +++ b/src/mono/wasm/host/Program.cs @@ -45,9 +45,14 @@ public static async Task Main(string[] args) { CommonConfiguration commonConfig = CommonConfiguration.FromCommandLineArguments(args); return !s_hostHandlers.TryGetValue(commonConfig.Host, out HostHandler? handler) - ? throw new Exception($"Cannot find any handler for host type {commonConfig.Host}") + ? throw new CommandLineException($"Cannot find any handler for host type {commonConfig.Host}") : await handler(commonConfig, loggerFactory, logger, cts.Token); } + catch (CommandLineException cle) + { + Console.WriteLine($"Error: {cle.Message}"); + return -1; + } catch (OptionException oe) { Console.WriteLine($"Error: {oe.Message}"); From 5d91acf43d7276aec47933af3833c0dbe5eb0e7b Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 18 May 2022 17:25:40 +0000 Subject: [PATCH 3/5] cleanup --- src/mono/wasm/Makefile | 2 +- src/mono/wasm/build/WasmApp.props | 2 +- src/mono/wasm/runtime/startup.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index d7d45547ad1252..7864488d0cb8e5 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -44,7 +44,7 @@ provision-wasm: .stamp-wasm-install-and-select-$(EMSCRIPTEN_VERSION) @echo "----------------------------------------------------------" @echo "Installed emsdk into EMSDK_PATH=$(TOP)/src/mono/wasm/emsdk" -MONO_OBJ_DIR=$(OBJDIR)/mono/browser.wasm.$(CONFIG) +MONO_OBJ_DIR=$(OBJDIR)/mono/Browser.wasm.$(CONFIG) BUILDS_OBJ_DIR=$(MONO_OBJ_DIR)/wasm clean-emsdk: diff --git a/src/mono/wasm/build/WasmApp.props b/src/mono/wasm/build/WasmApp.props index 88f479968888d5..7fdde27c4affb5 100644 --- a/src/mono/wasm/build/WasmApp.props +++ b/src/mono/wasm/build/WasmApp.props @@ -1,7 +1,7 @@ wasm - browser + Browser browser-wasm true diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 9395d6bd774f98..a24b168e8454ab 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -471,7 +471,7 @@ async function mono_download_assets(config: MonoConfig | MonoConfigError | undef runtimeHelpers.fetch = (config).fetch_file_cb; } - const max_parallel_downloads = 10; + const max_parallel_downloads = 100; // in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time let parallel_count = 0; let throttling_promise: Promise | undefined = undefined; From 2e8c00a9252d70cebb4d932e21b88dfd2391287b Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 18 May 2022 17:29:30 +0000 Subject: [PATCH 4/5] cleanup --- src/mono/wasm/host/WasmAppHost.csproj | 3 +-- src/mono/wasm/host/WasmHost.cs | 4 +++- .../BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/host/WasmAppHost.csproj b/src/mono/wasm/host/WasmAppHost.csproj index 45a3fcb2d61352..ece55d3763c43a 100644 --- a/src/mono/wasm/host/WasmAppHost.csproj +++ b/src/mono/wasm/host/WasmAppHost.csproj @@ -5,8 +5,7 @@ true $(NoWarn),CA2007 enable - LatestMajor - + diff --git a/src/mono/wasm/host/WasmHost.cs b/src/mono/wasm/host/WasmHost.cs index c137d1e60440eb..e3272ae6c6dcae 100644 --- a/src/mono/wasm/host/WasmHost.cs +++ b/src/mono/wasm/host/WasmHost.cs @@ -21,6 +21,8 @@ internal enum WasmHost /// NodeJS /// NodeJS, - + /// + /// Browser + /// Browser } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj index 155bf17d3486fe..99a9e68c37f558 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -30,7 +30,6 @@ - From 01a37da87e4f14605d9fa6e68c6587d5ecc29282 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Wed, 18 May 2022 21:14:06 +0000 Subject: [PATCH 5/5] Add back disabled jobs --- eng/pipelines/common/platform-matrix.yml | 1486 +++++++++++----------- 1 file changed, 743 insertions(+), 743 deletions(-) diff --git a/eng/pipelines/common/platform-matrix.yml b/eng/pipelines/common/platform-matrix.yml index 2c17628e8a37d9..02e2aff49d2403 100644 --- a/eng/pipelines/common/platform-matrix.yml +++ b/eng/pipelines/common/platform-matrix.yml @@ -28,267 +28,267 @@ parameters: jobs: # Linux arm -#- ${{ if or(containsValue(parameters.platforms, 'Linux_arm'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #archType: arm - #targetRid: linux-arm - #platform: Linux_arm - #container: - #image: ubuntu-18.04-cross-arm-20220426130400-6e40d49 - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #crossrootfsDir: '/crossrootfs/arm' - #${{ insert }}: ${{ parameters.jobParameters }} - -## Linux armv6 -#- ${{ if containsValue(parameters.platforms, 'Linux_armv6') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #archType: armv6 - #targetRid: linux-armv6 - #platform: Linux_armv6 - #container: - #image: ubuntu-20.04-cross-armv6-raspbian-10-20211208135931-e6e3ac4 - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #crossrootfsDir: '/crossrootfs/armv6' - #${{ insert }}: ${{ parameters.jobParameters }} - -## Linux arm64 - -#- ${{ if or(containsValue(parameters.platforms, 'Linux_arm64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #archType: arm64 - #targetRid: linux-arm64 - #platform: Linux_arm64 - #container: - #${{ if eq(parameters.container, '') }}: - #image: ubuntu-18.04-cross-arm64-20220426130400-6e40d49 - #${{ if ne(parameters.container, '') }}: - #image: ${{ parameters.container }} - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #crossrootfsDir: '/crossrootfs/arm64' - #${{ insert }}: ${{ parameters.jobParameters }} - -## Linux musl x64 - -#- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_x64'), eq(parameters.platformGroup, 'all')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #osSubgroup: _musl - #archType: x64 - #targetRid: linux-musl-x64 - #platform: Linux_musl_x64 - #container: - #image: alpine-3.13-WithNode-20210910135845-c401c85 - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Linux musl arm - -#- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_arm'), eq(parameters.platformGroup, 'all')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #osSubgroup: _musl - #archType: arm - #targetRid: linux-musl-arm - #platform: Linux_musl_arm - #container: - #image: ubuntu-16.04-cross-arm-alpine-20210923140502-78f7860 - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #crossrootfsDir: '/crossrootfs/arm' - #${{ insert }}: ${{ parameters.jobParameters }} - -## Linux musl arm64 - -#- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_arm64'), eq(parameters.platformGroup, 'all')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #osSubgroup: _musl - #archType: arm64 - #targetRid: linux-musl-arm64 - #platform: Linux_musl_arm64 - #container: - #image: ubuntu-16.04-cross-arm64-alpine-20210923140502-78f7860 - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #crossrootfsDir: '/crossrootfs/arm64' - #${{ insert }}: ${{ parameters.jobParameters }} - -## Linux x64 - -#- ${{ if or(containsValue(parameters.platforms, 'Linux_x64'), containsValue(parameters.platforms, 'CoreClrTestBuildHost'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #archType: x64 - #targetRid: linux-x64 - #platform: Linux_x64 - #container: - #${{ if eq(parameters.container, '') }}: - #image: centos-7-20210714125435-9b5bbc2 - #${{ if ne(parameters.container, '') }}: - #image: ${{ parameters.container }} - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Linux x86 - -#- ${{ if containsValue(parameters.platforms, 'Linux_x86') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #archType: x86 - #targetRid: linux-x86 - #platform: Linux_x86 - #container: - #image: ubuntu-18.04-cross-x86-linux-20211022152824-f853169 - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #crossrootfsDir: '/crossrootfs/x86' - #disableClrTest: true - #${{ insert }}: ${{ parameters.jobParameters }} - -## Linux x64 Source Build - -#- ${{ if containsValue(parameters.platforms, 'SourceBuild_Linux_x64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #archType: x64 - #targetRid: linux-x64 - #platform: Linux_x64 - #container: - #image: centos-7-source-build-20210714125450-5d87b80 - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - #buildingOnSourceBuildImage: true - -## Linux s390x - -#- ${{ if containsValue(parameters.platforms, 'Linux_s390x') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Linux - #archType: s390x - #targetRid: linux-s390x - #platform: Linux_s390x - #container: - #image: ubuntu-18.04-cross-s390x-20201102145728-d6e0352 - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #crossrootfsDir: '/crossrootfs/s390x' - #${{ insert }}: ${{ parameters.jobParameters }} +- ${{ if or(containsValue(parameters.platforms, 'Linux_arm'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + archType: arm + targetRid: linux-arm + platform: Linux_arm + container: + image: ubuntu-18.04-cross-arm-20220426130400-6e40d49 + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + crossrootfsDir: '/crossrootfs/arm' + ${{ insert }}: ${{ parameters.jobParameters }} + +# Linux armv6 +- ${{ if containsValue(parameters.platforms, 'Linux_armv6') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + archType: armv6 + targetRid: linux-armv6 + platform: Linux_armv6 + container: + image: ubuntu-20.04-cross-armv6-raspbian-10-20211208135931-e6e3ac4 + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + crossrootfsDir: '/crossrootfs/armv6' + ${{ insert }}: ${{ parameters.jobParameters }} + +# Linux arm64 + +- ${{ if or(containsValue(parameters.platforms, 'Linux_arm64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + archType: arm64 + targetRid: linux-arm64 + platform: Linux_arm64 + container: + ${{ if eq(parameters.container, '') }}: + image: ubuntu-18.04-cross-arm64-20220427171722-6e40d49 + ${{ if ne(parameters.container, '') }}: + image: ${{ parameters.container }} + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + crossrootfsDir: '/crossrootfs/arm64' + ${{ insert }}: ${{ parameters.jobParameters }} + +# Linux musl x64 + +- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_x64'), eq(parameters.platformGroup, 'all')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + osSubgroup: _musl + archType: x64 + targetRid: linux-musl-x64 + platform: Linux_musl_x64 + container: + image: alpine-3.13-WithNode-20210910135845-c401c85 + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Linux musl arm + +- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_arm'), eq(parameters.platformGroup, 'all')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + osSubgroup: _musl + archType: arm + targetRid: linux-musl-arm + platform: Linux_musl_arm + container: + image: ubuntu-16.04-cross-arm-alpine-20210923140502-78f7860 + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + crossrootfsDir: '/crossrootfs/arm' + ${{ insert }}: ${{ parameters.jobParameters }} + +# Linux musl arm64 + +- ${{ if or(containsValue(parameters.platforms, 'Linux_musl_arm64'), eq(parameters.platformGroup, 'all')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + osSubgroup: _musl + archType: arm64 + targetRid: linux-musl-arm64 + platform: Linux_musl_arm64 + container: + image: ubuntu-16.04-cross-arm64-alpine-20210923140502-78f7860 + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + crossrootfsDir: '/crossrootfs/arm64' + ${{ insert }}: ${{ parameters.jobParameters }} + +# Linux x64 + +- ${{ if or(containsValue(parameters.platforms, 'Linux_x64'), containsValue(parameters.platforms, 'CoreClrTestBuildHost'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + archType: x64 + targetRid: linux-x64 + platform: Linux_x64 + container: + ${{ if eq(parameters.container, '') }}: + image: centos-7-20210714125435-9b5bbc2 + ${{ if ne(parameters.container, '') }}: + image: ${{ parameters.container }} + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Linux x86 + +- ${{ if containsValue(parameters.platforms, 'Linux_x86') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + archType: x86 + targetRid: linux-x86 + platform: Linux_x86 + container: + image: ubuntu-18.04-cross-x86-linux-20211022152824-f853169 + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + crossrootfsDir: '/crossrootfs/x86' + disableClrTest: true + ${{ insert }}: ${{ parameters.jobParameters }} + +# Linux x64 Source Build + +- ${{ if containsValue(parameters.platforms, 'SourceBuild_Linux_x64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + archType: x64 + targetRid: linux-x64 + platform: Linux_x64 + container: + image: centos-7-source-build-20210714125450-5d87b80 + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + buildingOnSourceBuildImage: true + +# Linux s390x + +- ${{ if containsValue(parameters.platforms, 'Linux_s390x') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Linux + archType: s390x + targetRid: linux-s390x + platform: Linux_s390x + container: + image: ubuntu-18.04-cross-s390x-20201102145728-d6e0352 + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + crossrootfsDir: '/crossrootfs/s390x' + ${{ insert }}: ${{ parameters.jobParameters }} # WebAssembly @@ -360,485 +360,485 @@ jobs: ${{ insert }}: ${{ parameters.jobParameters }} # FreeBSD -#- ${{ if containsValue(parameters.platforms, 'FreeBSD_x64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: FreeBSD - #archType: x64 - #targetRid: freebsd-x64 - #platform: FreeBSD_x64 - #container: - #image: ubuntu-18.04-cross-freebsd-12-20210917001307-f13d79e - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #buildConfig: ${{ parameters.buildConfig }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #crossrootfsDir: '/crossrootfs/x64' - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Android x64 - -#- ${{ if containsValue(parameters.platforms, 'Android_x64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Android - #archType: x64 - #targetRid: android-x64 - #platform: Android_x64 - #container: - #image: ubuntu-18.04-android-20220131172314-3983b4e - #registry: mcr - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Android x86 - -#- ${{ if containsValue(parameters.platforms, 'Android_x86') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Android - #archType: x86 - #targetRid: android-x86 - #platform: Android_x86 - #container: - #image: ubuntu-18.04-android-20220131172314-3983b4e - #registry: mcr - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Android arm - -#- ${{ if containsValue(parameters.platforms, 'Android_arm') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Android - #archType: arm - #targetRid: android-arm - #platform: Android_arm - #container: - #image: ubuntu-18.04-android-20220131172314-3983b4e - #registry: mcr - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Android arm64 - -#- ${{ if containsValue(parameters.platforms, 'Android_arm64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Android - #archType: arm64 - #targetRid: android-arm64 - #platform: Android_arm64 - #container: - #image: ubuntu-18.04-android-20220131172314-3983b4e - #registry: mcr - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Mac Catalyst x64 - -#- ${{ if containsValue(parameters.platforms, 'MacCatalyst_x64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: MacCatalyst - #archType: x64 - #targetRid: maccatalyst-x64 - #platform: MacCatalyst_x64 - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Mac Catalyst arm64 - -#- ${{ if containsValue(parameters.platforms, 'MacCatalyst_arm64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: MacCatalyst - #archType: arm64 - #targetRid: maccatalyst-arm64 - #platform: MacCatalyst_arm64 - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## tvOS arm64 - -#- ${{ if containsValue(parameters.platforms, 'tvOS_arm64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: tvOS - #archType: arm64 - #targetRid: tvos-arm64 - #platform: tvOS_arm64 - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## tvOS Simulator x64 - -#- ${{ if containsValue(parameters.platforms, 'tvOSSimulator_x64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: tvOSSimulator - #archType: x64 - #targetRid: tvossimulator-x64 - #platform: tvOSSimulator_x64 - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## tvOS Simulator arm64 - -#- ${{ if containsValue(parameters.platforms, 'tvOSSimulator_arm64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: tvOSSimulator - #archType: arm64 - #targetRid: tvossimulator-arm64 - #platform: tvOSSimulator_arm64 - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## iOS arm - -#- ${{ if containsValue(parameters.platforms, 'iOS_arm') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: iOS - #archType: arm - #targetRid: ios-arm - #platform: iOS_arm - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## iOS arm64 - -#- ${{ if containsValue(parameters.platforms, 'iOS_arm64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: iOS - #archType: arm64 - #targetRid: ios-arm64 - #platform: iOS_arm64 - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## iOS Simulator x64 - -#- ${{ if containsValue(parameters.platforms, 'iOSSimulator_x64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: iOSSimulator - #archType: x64 - #targetRid: iossimulator-x64 - #platform: iOSSimulator_x64 - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## iOS Simulator x86 - -#- ${{ if containsValue(parameters.platforms, 'iOSSimulator_x86') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: iOSSimulator - #archType: x86 - #targetRid: iossimulator-x86 - #platform: iOSsimulator_x86 - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #managedTestBuildOsGroup: OSX - #${{ insert }}: ${{ parameters.jobParameters }} - -## iOS Simulator arm64 - -#- ${{ if containsValue(parameters.platforms, 'iOSSimulator_arm64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: iOSSimulator - #archType: arm64 - #targetRid: iossimulator-arm64 - #platform: iOSSimulator_arm64 - #jobParameters: - #runtimeFlavor: mono - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## macOS arm64 - -#- ${{ if containsValue(parameters.platforms, 'OSX_arm64') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: OSX - #archType: arm64 - #targetRid: osx-arm64 - #platform: OSX_arm64 - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #${{ insert }}: ${{ parameters.jobParameters }} - -## macOS x64 - -#- ${{ if or(containsValue(parameters.platforms, 'OSX_x64'), eq(parameters.platformGroup, 'all')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: OSX - #archType: x64 - #targetRid: osx-x64 - #platform: OSX_x64 - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Tizen armel - -#- ${{ if containsValue(parameters.platforms, 'Tizen_armel') }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: Tizen - #archType: armel - #targetRid: tizen-armel - #platform: Tizen_armel - #container: - #image: ubuntu-18.04-cross-armel-tizen-20210719212651-8b02f56 - #registry: mcr - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #crossBuild: true - #crossrootfsDir: '/crossrootfs/armel' - #disableClrTest: true - #${{ insert }}: ${{ parameters.jobParameters }} - -## Windows x64 - -#- ${{ if or(containsValue(parameters.platforms, 'windows_x64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: windows - #archType: x64 - #targetRid: win-x64 - #platform: windows_x64 - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Windows x86 - -#- ${{ if or(containsValue(parameters.platforms, 'windows_x86'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: windows - #archType: x86 - #targetRid: win-x86 - #platform: windows_x86 - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Windows arm -#- ${{ if or(containsValue(parameters.platforms, 'windows_arm'), eq(parameters.platformGroup, 'all')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: windows - #archType: arm - #targetRid: win-arm - #platform: windows_arm - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} - -## Windows arm64 - -#- ${{ if or(containsValue(parameters.platforms, 'windows_arm64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: - #- template: xplat-setup.yml - #parameters: - #jobTemplate: ${{ parameters.jobTemplate }} - #helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} - #variables: ${{ parameters.variables }} - #osGroup: windows - #archType: arm64 - #targetRid: win-arm64 - #platform: windows_arm64 - #jobParameters: - #runtimeFlavor: ${{ parameters.runtimeFlavor }} - #stagedBuild: ${{ parameters.stagedBuild }} - #buildConfig: ${{ parameters.buildConfig }} - #${{ if eq(parameters.passPlatforms, true) }}: - #platforms: ${{ parameters.platforms }} - #helixQueueGroup: ${{ parameters.helixQueueGroup }} - #${{ insert }}: ${{ parameters.jobParameters }} +- ${{ if containsValue(parameters.platforms, 'FreeBSD_x64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: FreeBSD + archType: x64 + targetRid: freebsd-x64 + platform: FreeBSD_x64 + container: + image: ubuntu-18.04-cross-freebsd-12-20210917001307-f13d79e + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + buildConfig: ${{ parameters.buildConfig }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + crossrootfsDir: '/crossrootfs/x64' + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Android x64 + +- ${{ if containsValue(parameters.platforms, 'Android_x64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Android + archType: x64 + targetRid: android-x64 + platform: Android_x64 + container: + image: ubuntu-18.04-android-20220131172314-3983b4e + registry: mcr + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Android x86 + +- ${{ if containsValue(parameters.platforms, 'Android_x86') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Android + archType: x86 + targetRid: android-x86 + platform: Android_x86 + container: + image: ubuntu-18.04-android-20220131172314-3983b4e + registry: mcr + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Android arm + +- ${{ if containsValue(parameters.platforms, 'Android_arm') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Android + archType: arm + targetRid: android-arm + platform: Android_arm + container: + image: ubuntu-18.04-android-20220131172314-3983b4e + registry: mcr + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Android arm64 + +- ${{ if containsValue(parameters.platforms, 'Android_arm64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Android + archType: arm64 + targetRid: android-arm64 + platform: Android_arm64 + container: + image: ubuntu-18.04-android-20220131172314-3983b4e + registry: mcr + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Mac Catalyst x64 + +- ${{ if containsValue(parameters.platforms, 'MacCatalyst_x64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: MacCatalyst + archType: x64 + targetRid: maccatalyst-x64 + platform: MacCatalyst_x64 + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Mac Catalyst arm64 + +- ${{ if containsValue(parameters.platforms, 'MacCatalyst_arm64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: MacCatalyst + archType: arm64 + targetRid: maccatalyst-arm64 + platform: MacCatalyst_arm64 + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# tvOS arm64 + +- ${{ if containsValue(parameters.platforms, 'tvOS_arm64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: tvOS + archType: arm64 + targetRid: tvos-arm64 + platform: tvOS_arm64 + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# tvOS Simulator x64 + +- ${{ if containsValue(parameters.platforms, 'tvOSSimulator_x64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: tvOSSimulator + archType: x64 + targetRid: tvossimulator-x64 + platform: tvOSSimulator_x64 + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# tvOS Simulator arm64 + +- ${{ if containsValue(parameters.platforms, 'tvOSSimulator_arm64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: tvOSSimulator + archType: arm64 + targetRid: tvossimulator-arm64 + platform: tvOSSimulator_arm64 + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# iOS arm + +- ${{ if containsValue(parameters.platforms, 'iOS_arm') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: iOS + archType: arm + targetRid: ios-arm + platform: iOS_arm + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# iOS arm64 + +- ${{ if containsValue(parameters.platforms, 'iOS_arm64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: iOS + archType: arm64 + targetRid: ios-arm64 + platform: iOS_arm64 + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# iOS Simulator x64 + +- ${{ if containsValue(parameters.platforms, 'iOSSimulator_x64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: iOSSimulator + archType: x64 + targetRid: iossimulator-x64 + platform: iOSSimulator_x64 + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# iOS Simulator x86 + +- ${{ if containsValue(parameters.platforms, 'iOSSimulator_x86') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: iOSSimulator + archType: x86 + targetRid: iossimulator-x86 + platform: iOSsimulator_x86 + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + managedTestBuildOsGroup: OSX + ${{ insert }}: ${{ parameters.jobParameters }} + +# iOS Simulator arm64 + +- ${{ if containsValue(parameters.platforms, 'iOSSimulator_arm64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: iOSSimulator + archType: arm64 + targetRid: iossimulator-arm64 + platform: iOSSimulator_arm64 + jobParameters: + runtimeFlavor: mono + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# macOS arm64 + +- ${{ if containsValue(parameters.platforms, 'OSX_arm64') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: OSX + archType: arm64 + targetRid: osx-arm64 + platform: OSX_arm64 + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + ${{ insert }}: ${{ parameters.jobParameters }} + +# macOS x64 + +- ${{ if or(containsValue(parameters.platforms, 'OSX_x64'), eq(parameters.platformGroup, 'all')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: OSX + archType: x64 + targetRid: osx-x64 + platform: OSX_x64 + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Tizen armel + +- ${{ if containsValue(parameters.platforms, 'Tizen_armel') }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: Tizen + archType: armel + targetRid: tizen-armel + platform: Tizen_armel + container: + image: ubuntu-18.04-cross-armel-tizen-20210719212651-8b02f56 + registry: mcr + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + crossBuild: true + crossrootfsDir: '/crossrootfs/armel' + disableClrTest: true + ${{ insert }}: ${{ parameters.jobParameters }} + +# Windows x64 + +- ${{ if or(containsValue(parameters.platforms, 'windows_x64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: windows + archType: x64 + targetRid: win-x64 + platform: windows_x64 + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Windows x86 + +- ${{ if or(containsValue(parameters.platforms, 'windows_x86'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: windows + archType: x86 + targetRid: win-x86 + platform: windows_x86 + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Windows arm +- ${{ if or(containsValue(parameters.platforms, 'windows_arm'), eq(parameters.platformGroup, 'all')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: windows + archType: arm + targetRid: win-arm + platform: windows_arm + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }} + +# Windows arm64 + +- ${{ if or(containsValue(parameters.platforms, 'windows_arm64'), in(parameters.platformGroup, 'all', 'gcstress')) }}: + - template: xplat-setup.yml + parameters: + jobTemplate: ${{ parameters.jobTemplate }} + helixQueuesTemplate: ${{ parameters.helixQueuesTemplate }} + variables: ${{ parameters.variables }} + osGroup: windows + archType: arm64 + targetRid: win-arm64 + platform: windows_arm64 + jobParameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + stagedBuild: ${{ parameters.stagedBuild }} + buildConfig: ${{ parameters.buildConfig }} + ${{ if eq(parameters.passPlatforms, true) }}: + platforms: ${{ parameters.platforms }} + helixQueueGroup: ${{ parameters.helixQueueGroup }} + ${{ insert }}: ${{ parameters.jobParameters }}