|
5 | 5 | from pathlib import Path |
6 | 6 | from collections.abc import Iterator |
7 | 7 |
|
| 8 | +from ctypes.util import find_library |
| 9 | + |
8 | 10 | from ..types import StrOrPath |
9 | 11 |
|
10 | 12 | from .runtime_spec import DotnetCoreRuntimeSpec |
@@ -107,47 +109,85 @@ def find_runtimes() -> Iterator[DotnetCoreRuntimeSpec]: |
107 | 109 | return find_runtimes_in_root(dotnet_root) |
108 | 110 |
|
109 | 111 |
|
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: |
111 | 115 | """Find a suitable libmono dynamic library |
112 | 116 |
|
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. |
114 | 120 |
|
| 121 | + :param assembly_dir: |
| 122 | + Optional directory to search for libmono |
115 | 123 | :param sgen: |
116 | 124 | Whether to look for an SGen or Boehm GC instance. This parameter is |
117 | 125 | ignored on Windows, as only ``sgen`` is installed with the default |
118 | 126 | installer |
119 | 127 | :return: |
120 | 128 | Path to usable ``libmono`` |
121 | 129 | """ |
122 | | - unix_name = f"mono{'sgen' if sgen else ''}-2.0" |
| 130 | + |
123 | 131 | 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() |
128 | 133 |
|
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) |
131 | 136 |
|
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 | | - ) |
140 | 137 |
|
| 138 | +def _find_mono_windows() -> Path | None: |
| 139 | + if sys.maxsize > 2**32: |
| 140 | + prog_files = os.environ.get("ProgramFiles") |
141 | 141 | else: |
142 | | - if assembly_dir is None: |
143 | | - from ctypes.util import find_library |
| 142 | + prog_files = os.environ.get("ProgramFiles(x86)") |
144 | 143 |
|
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") |
149 | 146 |
|
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 |
152 | 192 |
|
153 | | - return Path(path) |
| 193 | + raise RuntimeError("Could not find libmono") |
0 commit comments