Skip to content

Commit dce4ed9

Browse files
committed
Improve find_mono on macOS
1 parent 1f68005 commit dce4ed9

2 files changed

Lines changed: 66 additions & 40 deletions

File tree

.github/workflows/ci.yml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,20 +77,6 @@ jobs:
7777
python: ['3.14', '3.13', '3.12', '3.11', '3.10']
7878

7979
exclude:
80-
# Broken ctypes find_library
81-
- os:
82-
category: macos
83-
platform: arm64
84-
python: '3.10'
85-
- os:
86-
category: macos
87-
platform: arm64
88-
python: '3.11'
89-
- os:
90-
category: macos
91-
platform: arm64
92-
python: '3.12'
93-
9480
# Fails to call mono methods
9581
- os:
9682
category: windows

clr_loader/util/find.py

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from pathlib import Path
66
from collections.abc import Iterator
77

8+
from ctypes.util import find_library
9+
810
from ..types import StrOrPath
911

1012
from .runtime_spec import DotnetCoreRuntimeSpec
@@ -107,47 +109,85 @@ def find_runtimes() -> Iterator[DotnetCoreRuntimeSpec]:
107109
return find_runtimes_in_root(dotnet_root)
108110

109111

110-
def find_libmono(*, assembly_dir: StrOrPath | None = None, sgen: bool = True) -> Path: # noqa: F821
112+
def find_libmono(
113+
*, assembly_dir: StrOrPath | None = None, sgen: bool = True
114+
) -> Path | None:
111115
"""Find a suitable libmono dynamic library
112116
113-
On Windows and macOS, we check the default installation directories.
117+
On Windows, we check the default installation directory.
118+
On macOS, we check Homebrew (with architecture awareness) and the framework.
119+
On Unix-like systems, we check Homebrew and system locations.
114120
121+
:param assembly_dir:
122+
Optional directory to search for libmono
115123
:param sgen:
116124
Whether to look for an SGen or Boehm GC instance. This parameter is
117125
ignored on Windows, as only ``sgen`` is installed with the default
118126
installer
119127
:return:
120128
Path to usable ``libmono``
121129
"""
122-
unix_name = f"mono{'sgen' if sgen else ''}-2.0"
130+
123131
if sys.platform == "win32":
124-
if sys.maxsize > 2**32:
125-
prog_files = os.environ.get("ProgramFiles")
126-
else:
127-
prog_files = os.environ.get("ProgramFiles(x86)")
132+
return _find_mono_windows()
128133

129-
if prog_files is None:
130-
raise RuntimeError("Could not determine Program Files location")
134+
else:
135+
return _find_mono_unix(assembly_dir=assembly_dir, sgen=sgen)
131136

132-
# Ignore sgen on Windows, the main installation only contains this DLL
133-
path = Path(prog_files) / "Mono/bin/mono-2.0-sgen.dll"
134-
135-
elif sys.platform == "darwin":
136-
path = (
137-
Path("/Library/Frameworks/Mono.framework/Versions/Current/lib")
138-
/ f"lib{unix_name}.dylib"
139-
)
140137

138+
def _find_mono_windows() -> Path | None:
139+
if sys.maxsize > 2**32:
140+
prog_files = os.environ.get("ProgramFiles")
141141
else:
142-
if assembly_dir is None:
143-
from ctypes.util import find_library
142+
prog_files = os.environ.get("ProgramFiles(x86)")
144143

145-
path = find_library(unix_name)
146-
else:
147-
libname: str = "lib" + unix_name + ".so"
148-
path = Path(assembly_dir) / "lib" / libname
144+
if prog_files is None:
145+
raise RuntimeError("Could not determine Program Files location")
149146

150-
if path is None:
151-
raise RuntimeError("Could not find libmono")
147+
# Ignore sgen on Windows, the main installation only contains this DLL
148+
return Path(prog_files) / "Mono/bin/mono-2.0-sgen.dll"
149+
150+
151+
def _find_mono_unix(
152+
*, assembly_dir: StrOrPath | None = None, sgen: bool = True
153+
) -> Path | None:
154+
macos = sys.platform == "darwin"
155+
156+
unix_name = f"mono{'sgen' if sgen else ''}-2.0"
157+
ext = ".dylib" if macos else ".so"
158+
159+
lib_filename = f"lib{unix_name}{ext}"
160+
161+
if assembly_dir is not None:
162+
candidate = Path(assembly_dir) / "lib" / lib_filename
163+
if candidate.exists():
164+
return candidate
165+
166+
if res := find_library(unix_name):
167+
return Path(res)
168+
169+
if macos:
170+
res = (
171+
Path("/Library/Frameworks/Mono.framework/Versions/Current/lib")
172+
/ lib_filename
173+
)
174+
if res.exists():
175+
return res
176+
177+
# Use HOMEBREW_PREFIX environment variable if available
178+
if homebrew_prefix := os.environ.get("HOMEBREW_PREFIX"):
179+
res = Path(homebrew_prefix) / "opt/mono/lib" / lib_filename
180+
if res.exists():
181+
return res
182+
183+
# Check for native Apple Silicon (arm64)
184+
if platform.machine() == "arm64":
185+
res = Path("/opt/homebrew/opt/mono/lib") / lib_filename
186+
if res.exists():
187+
return res
188+
else:
189+
res = Path("/usr/local/opt/mono/lib") / lib_filename
190+
if res.exists():
191+
return res
152192

153-
return Path(path)
193+
raise RuntimeError("Could not find libmono")

0 commit comments

Comments
 (0)