From 8b2f4cfb049cfd8f9a1dda08f62e696457410f9e Mon Sep 17 00:00:00 2001 From: Alex Nordlund Date: Wed, 31 Jul 2024 14:18:33 +0200 Subject: [PATCH] Add support for ARM64 Windows #315 This adds initial support for ARM64, but it's slightly limited as I couldn't get acquire a compatible device. The powershell invocation is somewhat slow (sometimes taking almost 700ms with my profile), which is unfortunate becuase it'd be way smoother to parse than the current os.arch solution --- CHANGELOG.md | 3 + .../com/github/gradle/node/NodePlugin.kt | 32 ++++++--- .../github/gradle/node/util/PlatformHelper.kt | 63 ++++++++++++++++- .../node/util/PlatformHelperTest.groovy | 67 ++++++++----------- 4 files changed, 117 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aafac324..46c4ccd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Version 7.x *(unreleased)* +## Version 7.0.3 *(unreleased)* +* Add support for ARM64 Windows [#315](https://github.com/node-gradle/gradle-node-plugin/issues/315) + ## Version 7.0.2 *(2024-02-02)* * Prevent misconfigured `workDir` from removing all unrelated files [#297](https://github.com/node-gradle/gradle-node-plugin/issues/297) diff --git a/src/main/kotlin/com/github/gradle/node/NodePlugin.kt b/src/main/kotlin/com/github/gradle/node/NodePlugin.kt index d9a7855c..ed10b953 100644 --- a/src/main/kotlin/com/github/gradle/node/NodePlugin.kt +++ b/src/main/kotlin/com/github/gradle/node/NodePlugin.kt @@ -16,10 +16,12 @@ import com.github.gradle.node.variant.computeNodeDir import com.github.gradle.node.yarn.task.YarnInstallTask import com.github.gradle.node.yarn.task.YarnSetupTask import com.github.gradle.node.yarn.task.YarnTask +import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.provider.Property import org.gradle.kotlin.dsl.* +import org.gradle.process.ExecSpec import org.gradle.util.GradleVersion import java.io.ByteArrayOutputStream import java.io.File @@ -62,18 +64,32 @@ class NodePlugin : Plugin { } private fun addPlatform(extension: NodeExtension) { + val osType = parseOsType(System.getProperty("os.name")) + val arch = System.getProperty("os.arch") + + val unameSpec: Action = Action { + if (osType == OsType.WINDOWS) { + this.executable = "powershell" + this.args = listOf( + "-NoProfile", // Command runs in ~175ms, -NoProfile saves ~300ms + "-Command", + "(Get-WmiObject Win32_Processor).Architecture", + ) + } else { + this.executable = "uname" + this.args = listOf("-m") + } + } + val uname = { if (GradleVersion.current() >= GradleVersion.version("7.5")) { - val cmd = project.providers.exec { - this.executable = "uname" - this.args = listOf("-m") - } + val cmd = project.providers.exec(unameSpec) cmd.standardOutput.asText.get().trim() } else { val out = ByteArrayOutputStream() + project.exec(unameSpec) val cmd = project.exec { - this.executable = "uname" - this.args = listOf("-m") + unameSpec.execute(this) this.standardOutput = out } @@ -81,9 +97,7 @@ class NodePlugin : Plugin { out.toString().trim() } } - val name = System.getProperty("os.name") - val arch = System.getProperty("os.arch") - val platform = parsePlatform(name, arch, uname) + val platform = parsePlatform(osType, arch, uname) extension.resolvedPlatform.set(platform) extension.computedPlatform.convention(extension.resolvedPlatform) } diff --git a/src/main/kotlin/com/github/gradle/node/util/PlatformHelper.kt b/src/main/kotlin/com/github/gradle/node/util/PlatformHelper.kt index b25e27e0..b17f64b4 100644 --- a/src/main/kotlin/com/github/gradle/node/util/PlatformHelper.kt +++ b/src/main/kotlin/com/github/gradle/node/util/PlatformHelper.kt @@ -2,6 +2,32 @@ package com.github.gradle.node.util import java.util.concurrent.Callable +internal enum class OsType(val osName: String) { + WINDOWS("win"), + MAC("darwin"), + LINUX("linux"), + FREEBSD("linux"), // https://github.com/node-gradle/gradle-node-plugin/issues/178 + SUN("sunos"), +} + +internal fun parsePlatform(type: OsType, arch: String, uname: () -> String): Platform { + val osArch = if (type == OsType.WINDOWS) parseWindowsArch(arch.toLowerCase(), uname) + else parseOsArch(arch.toLowerCase(), uname) + return Platform(type.osName, osArch) +} + +internal fun parseOsType(type: String): OsType { + val name = type.toLowerCase() + return when { + name.contains("windows") -> OsType.WINDOWS + name.contains("mac") -> OsType.MAC + name.contains("linux") -> OsType.LINUX + name.contains("freebsd") -> OsType.FREEBSD + name.contains("sunos") -> OsType.SUN + else -> error("Unsupported OS: $name") + } +} + fun parsePlatform(name: String, arch: String, uname: () -> String): Platform { return Platform(parseOsName(name.toLowerCase()), parseOsArch(arch.toLowerCase(), uname)) } @@ -33,10 +59,45 @@ fun parseOsArch(arch: String, uname: Callable): String { } } +fun parseWindowsArch(arch: String, uname: Callable): String { + // + return when { + arch.startsWith("aarch") || arch.startsWith("arm") + -> { + val wmiArch = uname.call() + return when (wmiArch) { + /* + * Parse Win32_Processor.Architectures to real processor type + * + * Table from https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info#members + */ + "12" -> "arm64" + "9" -> "x64" + // "6" -> "IA64" + // "5" -> "arm" // 32-bit + "0" -> "x86" + // "0xffff" -> "Unknown" + else -> error("Unexpected Win32_Processor.Architecture: $arch") + } + } + arch.contains("64") -> "x64" + else -> "x86" + } +} + fun main(args: Array) { val osName = System.getProperty("os.name") val osArch = System.getProperty("os.arch") - val uname = { execute("uname", "-m", timeout = 10) } + + val osType = parseOsType(osName) + val uname = { + val args = if (osType == OsType.WINDOWS) { + listOf("powershell", "-NoProfile", "-Command", "(Get-WmiObject Win32_Processor).Architecture") + } else { + listOf("uname", "-m") + } + execute(*args.toTypedArray(), timeout = 10) + } val platform = parsePlatform(osName, osArch, uname) println("Your os.name is: '${osName}' and is parsed as: '${platform.name}'") diff --git a/src/test/groovy/com/github/gradle/node/util/PlatformHelperTest.groovy b/src/test/groovy/com/github/gradle/node/util/PlatformHelperTest.groovy index dd2f413e..37aa3d6b 100644 --- a/src/test/groovy/com/github/gradle/node/util/PlatformHelperTest.groovy +++ b/src/test/groovy/com/github/gradle/node/util/PlatformHelperTest.groovy @@ -16,53 +16,44 @@ class PlatformHelperTest extends Specification { platform.windows == isWindows where: - osProp | archProp | osName | osArch | isWindows - 'Windows 8' | 'x86' | 'win' | 'x86' | true - 'Windows 8' | 'x86_64' | 'win' | 'x64' | true - 'Mac OS X' | 'x86' | 'darwin' | 'x86' | false - 'Mac OS X' | 'x86_64' | 'darwin' | 'x64' | false - 'Linux' | 'x86' | 'linux' | 'x86' | false - 'Linux' | 'x86_64' | 'linux' | 'x64' | false - 'Linux' | 'ppc64le' | 'linux' | 'ppc64le' | false - 'Linux' | 's390x' | 'linux' | 's390x' | false - 'SunOS' | 'x86' | 'sunos' | 'x86' | false - 'SunOS' | 'x86_64' | 'sunos' | 'x64' | false + osProp | archProp || osName | osArch | isWindows + 'Windows 8' | 'x86' || 'win' | 'x86' | true + 'Windows 8' | 'x86_64' || 'win' | 'x64' | true + 'Windows 10' | 'x86_64' || 'win' | 'x64' | true + 'Mac OS X' | 'x86' || 'darwin' | 'x86' | false + 'Mac OS X' | 'x86_64' || 'darwin' | 'x64' | false + 'Linux' | 'x86' || 'linux' | 'x86' | false + 'Linux' | 'x86_64' || 'linux' | 'x64' | false + 'Linux' | 'ppc64le' || 'linux' | 'ppc64le' | false + 'Linux' | 's390x' || 'linux' | 's390x' | false + 'SunOS' | 'x86' || 'sunos' | 'x86' | false + 'SunOS' | 'x86_64' || 'sunos' | 'x64' | false } @Unroll - def "verify ARM handling #archProp (#unameProp)"() { + def "verify #osProp ARM handling #archProp (#unameProp)"() { given: - def platform = PlatformHelperKt.parsePlatform("Linux", archProp, { unameProp }) + def osType = PlatformHelperKt.parseOsType(osProp) + def platform = PlatformHelperKt.parsePlatform(osType, archProp, { unameProp }) expect: - platform.name == "linux" - platform.arch == osArch - - where: - archProp | unameProp | osArch - 'arm' | 'armv7l' | 'armv7l' // Raspberry Pi 3 - 'arm' | 'armv8l' | 'arm64' - 'aarch32' | 'arm' | 'arm' - 'aarch64' | 'arm64' | 'arm64' - 'aarch64' | 'aarch64' | 'arm64' - 'ppc64le' | 'ppc64le' | 'ppc64le' - } - - @Unroll - def "verify ARM handling Mac OS #archProp (#unameProp)"() { - given: - def platform = PlatformHelperKt.parsePlatform("Mac OS X", archProp, { unameProp }) - - expect: - platform.name == "darwin" + platform.name == osName platform.arch == osArch where: - archProp | unameProp | osArch - 'aarch32' | 'arm' | 'arm' - 'aarch64' | 'arm64' | 'arm64' - 'aarch64' | 'aarch64' | 'arm64' - 'aarch64' | 'x86_64' | 'x64' // This shouldn't really happen but according to PR #204 it does + osProp | archProp || osName | unameProp | osArch + 'Linux' | 'arm' || 'linux' | 'armv7l' | 'armv7l' // Raspberry Pi 3 + 'Linux' | 'arm' || 'linux' | 'armv8l' | 'arm64' + 'Linux' | 'aarch32' || 'linux' | 'arm' | 'arm' + 'Linux' | 'aarch64' || 'linux' | 'arm64' | 'arm64' + 'Linux' | 'aarch64' || 'linux' | 'aarch64' | 'arm64' + 'Linux' | 'ppc64le' || 'linux' | 'ppc64le' | 'ppc64le' + 'Mac OS X' | 'aarch32' || 'darwin' | 'arm' | 'arm' + 'Mac OS X' | 'aarch64' || 'darwin' | 'arm64' | 'arm64' + 'Mac OS X' | 'aarch64' || 'darwin' | 'aarch64' | 'arm64' + 'Mac OS X' | 'aarch64' || 'darwin' | 'x86_64' | 'x64' // This unfortunately happens see PR #204 + 'Windows 10' | 'aarch64' || 'win' | '12' | 'arm64' + 'Windows 11' | 'aarch64' || 'win' | '9' | 'x64' // Not sure if this can actually happen } def "throw exception if unsupported os"() {