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

Make choosenim produce x86_64/arm64 proxies on Apple Silicon machines #10

Merged
merged 3 commits into from
Aug 29, 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
nimcache
bin/choosenim
src/choosenimpkg/proxyexe
src/choosenimpkg/proxyexe-amd64
src/choosenimpkg/proxyexe-arm64
*.exe

tests/nimcache
Expand Down
16 changes: 16 additions & 0 deletions src/choosenim.nims
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,21 @@ when defined(macosx):
elif not defined(windows):
switch("define", "curl")

import strutils

proc isRosetta*(): bool =
let res = gorgeEx("sysctl -in sysctl.proc_translated")
if res.exitCode == 0:
return res.output.strip() == "1"
return false

proc isAppleSilicon(): bool =
let (output, exitCode) = gorgeEx("uname -m") # arch -x86_64 uname -m returns x86_64 on M1
assert exitCode == 0, output
return output.strip() == "arm64" or isRosetta()

when defined(macosx) and isAppleSilicon():
switch("passC", "-Wno-incompatible-function-pointer-types")

when defined(staticBuild):
import "choosenimpkg/proxyexe.nims"
72 changes: 66 additions & 6 deletions src/choosenimpkg/switcher.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,89 @@ import os, strutils, osproc, pegs
import nimblepkg/[cli, version, options]
from nimblepkg/tools import getNameVersionChecksum

import cliparams, common
import cliparams, common, utils

when defined(windows):
import env

proc compileProxyexe() =
proc compileProxyexe(additionalMacOSFlag: string = "", proxyName = "proxyexe") =
var cmd =
when defined(windows):
"cmd /C \"cd ../../ && nimble c"
elif defined(macosx):
"cd ../../ && nimble c " & additionalMacOSFlag
else:
"cd ../../ && nimble c"
when defined(release):
cmd.add " -d:release"
when defined(staticBuild):
cmd.add " -d:staticBuild"
cmd.add " src/choosenimpkg/proxyexe"
cmd.add " src/choosenimpkg/proxyexe --out:src/choosenimpkg/" & proxyName
when defined(windows):
cmd.add("\"")
let (output, exitCode) = gorgeEx(cmd)
doAssert exitCode == 0, $(output, cmd)

static: compileProxyexe()
proc isMacOSBelowBigSurCompileTime(): bool =
const (versionOutput, _) = gorgeEx("sw_vers -productVersion")
const currentVersion = versionOutput.split(".")

const
proxyExe = staticRead("proxyexe".addFileExt(ExeExt))
if currentVersion.len() < 1: return false # version should be at least two digit like `11`
let twoVersion = parseFloat(
if currentVersion.len() == 1:
currentVersion[0].strip()
else: currentVersion[0..1].join(".").strip())

return twoVersion < 11

proc isMacOSBelowBigSur(): bool =
let (versionOutput, _) = execCmdEx("sw_vers -productVersion")
let currentVersion = versionOutput.split(".")

if currentVersion.len() < 1:
return false # version should be at least two digit like `11`
let twoVersion = parseFloat(
if currentVersion.len() == 1:
currentVersion[0].strip()
else: currentVersion[0..1].join(".").strip())

return twoVersion < 11

proc isAppleSilicon(): bool =
let (output, exitCode) = execCmdEx("uname -m") # arch -x86_64 uname -m returns x86_64 on M1
assert exitCode == 0, output
return output.strip() == "arm64" or isRosetta()

static:
when defined(macosx):
compileProxyexe("--cpu:amd64 --passC:'-arch x86_64' --passL:'-arch x86_64'", "proxyexe-amd64")
# if CI or building machine is below macOS Big Sur, don't compile cross-compile arm64 proxyexe
when not isMacOSBelowBigSurCompileTime():
compileProxyexe("--cpu:arm64 --passC:'-arch arm64' --passL:'-arch arm64'", "proxyexe-arm64")
else:
compileProxyexe()

when defined(macosx):
when not isMacOSBelowBigSurCompileTime():
const embeddedProxyExeArm: string = staticRead("proxyexe-arm64".addFileExt(ExeExt))
else:
{.warning: "Since choosenim is compiled on macOS version below BigSur, choosenim won't be able to produce arm64 proxies.".}
const embeddedProxyExe = staticRead("proxyexe-amd64".addFileExt(ExeExt))
else:
const embeddedproxyExe: string = staticRead("proxyexe".addFileExt(ExeExt))

proc proxyToUse(): string =
when defined(macosx):
# if user machine is running big sur and above, we have to check if it's Apple Silicon
if not isMacOSBelowBigSur() and isAppleSilicon():
when declared(embeddedProxyExeArm):
display("Debug:", "Using arm64 proxy", Warning, DebugPriority)
return embeddedProxyExeArm
# embeddedProxyExeArm doesn't exist means choosenim was compiled on machine with macOS version below BigSur
display("Warning:", "Since choosenim is compiled on macOS version below BigSur, choosenim won't be able to install arm64 proxies.", Warning, DebugPriority)
# normal linux, windows build ( the same as macos amd64 )
display("Debug:", "Using x86_64 proxy", Warning, DebugPriority)
return embeddedProxyExe

proc getInstallationDir*(params: CliParams, version: Version): string =
return params.getInstallDir() / ("nim-$1" % $version)
Expand All @@ -45,6 +103,7 @@ proc getProxyPath(params: CliParams, bin: string): string =

proc areProxiesInstalled(params: CliParams, proxies: openarray[string]): bool =
result = true
let proxyExe = proxyToUse()
for proxy in proxies:
# Verify that proxy exists.
let path = params.getProxyPath(proxy)
Expand Down Expand Up @@ -133,6 +192,7 @@ proc writeProxy(bin: string, params: CliParams) =
display("Removed", "symlink pointing to $1" % symlinkPath,
priority = HighPriority)

let proxyExe = proxyToUse()
# Don't write the file again if it already exists.
if fileExists(proxyPath) and readFile(proxyPath) == proxyExe: return

Expand Down
21 changes: 17 additions & 4 deletions src/choosenimpkg/utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,31 @@ proc parseVersion*(versionStr: string): Version =

result = newVersion(versionStr)

proc isRosetta*(): bool =
let res = execCmdEx("sysctl -in sysctl.proc_translated")
if res.exitCode == 0:
return res.output.strip() == "1"
return false

proc doCmdRaw*(cmd: string) =
var command = cmd
# To keep output in sequence
stdout.flushFile()
stderr.flushFile()

displayDebug("Executing", cmd)
if defined(macosx) and isRosetta():
command = "arch -arm64 " & command

displayDebug("Executing", command)
displayDebug("Work Dir", getCurrentDir())
let (output, exitCode) = execCmdEx(cmd)
let (output, exitCode) = execCmdEx(command)
displayDebug("Finished", "with exit code " & $exitCode)
displayDebug("Output", output)

if exitCode != QuitSuccess:
raise newException(ChooseNimError,
"Execution failed with exit code $1\nCommand: $2\nOutput: $3" %
[$exitCode, cmd, output])
[$exitCode, command, output])

proc extract*(path: string, extractDir: string) =
display("Extracting", path.extractFilename(), priority = HighPriority)
Expand Down Expand Up @@ -188,7 +198,10 @@ proc getNightliesUrl*(parsedContents: JsonNode, arch: int): (string, string) =
if "x" & $arch in aname:
result = (url, tagName)
else:
result = (url, tagName)
# when choosenim become arm64 binary, isRosetta will be false. But we don't have nightlies for arm64 yet.
# So, we should check if choosenim is compiled as x86_64 (nim's system.hostCPU returns amd64 even on Apple Silicon machines)
if not isRosetta() and hostCPU == "amd64":
result = (url, tagName)
if result[0].len != 0:
break
if result[0].len != 0:
Expand Down