Skip to content

Commit

Permalink
Merge pull request #10 from heinthanth/master
Browse files Browse the repository at this point in the history
Make choosenim produce x86_64/arm64 proxies on Apple Silicon machines
  • Loading branch information
ringabout authored Aug 29, 2024
2 parents a536e56 + d78e520 commit 680dea6
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 10 deletions.
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

0 comments on commit 680dea6

Please sign in to comment.