Skip to content

Commit

Permalink
Add support for ARM64 Windows #315
Browse files Browse the repository at this point in the history
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
  • Loading branch information
deepy committed Jul 31, 2024
1 parent 70eb5c1 commit 8b2f4cf
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 48 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
32 changes: 23 additions & 9 deletions src/main/kotlin/com/github/gradle/node/NodePlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -62,28 +64,40 @@ class NodePlugin : Plugin<Project> {
}

private fun addPlatform(extension: NodeExtension) {
val osType = parseOsType(System.getProperty("os.name"))
val arch = System.getProperty("os.arch")

val unameSpec: Action<ExecSpec> = 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
}

cmd.assertNormalExitValue()
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)
}
Expand Down
63 changes: 62 additions & 1 deletion src/main/kotlin/com/github/gradle/node/util/PlatformHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -33,10 +59,45 @@ fun parseOsArch(arch: String, uname: Callable<String>): String {
}
}

fun parseWindowsArch(arch: String, uname: Callable<String>): 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<String>) {
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}'")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"() {
Expand Down

0 comments on commit 8b2f4cf

Please sign in to comment.