Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ARM64 Windows #318

Merged
merged 1 commit into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading