diff --git a/pythonfmu3/osutil.py b/pythonfmu3/osutil.py index 568350e..6052eed 100644 --- a/pythonfmu3/osutil.py +++ b/pythonfmu3/osutil.py @@ -1,14 +1,40 @@ +import platform import sys -import platform +# FMI 3.0 platform tuples are "-" (FMI 3.0 standard, section 2.5.1.4), +# e.g. x86_64-linux, aarch64-darwin, x86-windows. +_SYSTEMS = {"Windows": "windows", "Linux": "linux", "Darwin": "darwin"} + +_ARCHITECTURES_64 = { + "x86_64": "x86_64", "amd64": "x86_64", "x64": "x86_64", + "i386": "x86_64", "i686": "x86_64", "x86": "x86_64", # 64-bit Python on x86 hardware + "aarch64": "aarch64", "arm64": "aarch64", "armv8l": "aarch64", + "ppc64": "ppc64", "ppc64le": "ppc64", + "riscv64": "riscv64", +} + +_ARCHITECTURES_32 = { + "x86_64": "x86", "amd64": "x86", "x64": "x86", "i386": "x86", "i686": "x86", "x86": "x86", + "aarch64": "aarch32", "arm64": "aarch32", "armv7l": "aarch32", "armv8l": "aarch32", "arm": "aarch32", + "ppc": "ppc32", + "riscv32": "riscv32", +} + + +def _is_64bit() -> bool: + # Bitness of the running interpreter, which is what the FMU binary must match + # (e.g. a 32-bit Python on 64-bit Windows needs x86-windows). + return sys.maxsize > 2 ** 32 + + +def get_fmu_arch() -> str: + """FMI 3.0 platform tuple of the running interpreter, e.g. 'aarch64-darwin'.""" + machine = platform.machine().lower() + table = _ARCHITECTURES_64 if _is_64bit() else _ARCHITECTURES_32 + arch = table.get(machine, machine or "unknown") + system = _SYSTEMS.get(platform.system(), platform.system().lower() or "unknown") + return f"{arch}-{system}" -def get_fmu_arch(): - #is_64bits = sys.maxsize > 2 ** 32 - system = platform.system() - is_x86 = platform.machine() in ["i386", "AMD64", "x86_64"] - platforms = {"Windows": "windows", "Linux": "linux", "Darwin": "darwin"} - arch = "x86_64" if is_x86 else "x86" - return arch + "-" + platforms.get(system, "unknown") def get_platform() -> str: """Get FMU binary platform folder name.""" @@ -19,4 +45,3 @@ def get_lib_extension() -> str: """Get FMU library platform extension.""" platforms = {"Darwin": "dylib", "Linux": "so", "Windows": "dll"} return platforms.get(platform.system(), "") - diff --git a/pythonfmu3/pythonfmu-export/CMakeLists.txt b/pythonfmu3/pythonfmu-export/CMakeLists.txt index 8b48b84..8f68295 100644 --- a/pythonfmu3/pythonfmu-export/CMakeLists.txt +++ b/pythonfmu3/pythonfmu-export/CMakeLists.txt @@ -48,12 +48,44 @@ else () set(TARGET_PLATFORM linux) endif () -if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") - set(TARGET_PLATFORM x86_64-${TARGET_PLATFORM}) +# FMI 3.0 platform tuple -; must match pythonfmu3/osutil.py:get_fmu_arch(). +# The architecture is the one being *built for*: the Visual Studio -A option, or a single +# CMAKE_OSX_ARCHITECTURES entry on macOS, take precedence over the host processor. +if (CMAKE_GENERATOR_PLATFORM) + set(_arch "${CMAKE_GENERATOR_PLATFORM}") +elseif (APPLE AND CMAKE_OSX_ARCHITECTURES) + list(LENGTH CMAKE_OSX_ARCHITECTURES _n_arch) + if (NOT _n_arch EQUAL 1) + message(FATAL_ERROR "Universal builds are not supported: set CMAKE_OSX_ARCHITECTURES to a single architecture") + endif () + set(_arch "${CMAKE_OSX_ARCHITECTURES}") +else () + set(_arch "${CMAKE_SYSTEM_PROCESSOR}") +endif () +string(TOLOWER "${_arch}" _arch) + +if (_arch MATCHES "^(aarch64|arm64|armv8)") + set(_fmi_arch aarch64) +elseif (_arch MATCHES "^arm") + set(_fmi_arch aarch32) +elseif (_arch MATCHES "^(x86_64|amd64|x64|i[3-6]86|x86|win32)$") + set(_fmi_arch x86) else () - set(TARGET_PLATFORM x86-${TARGET_PLATFORM}) + set(_fmi_arch "${_arch}") endif () +if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + if (_fmi_arch STREQUAL "x86") + set(_fmi_arch x86_64) + elseif (_fmi_arch STREQUAL "aarch32") + set(_fmi_arch aarch64) + endif () +elseif (_fmi_arch STREQUAL "aarch64") + set(_fmi_arch aarch32) +endif () + +set(TARGET_PLATFORM ${_fmi_arch}-${TARGET_PLATFORM}) + message("Building pythonfmu-export for platform ${TARGET_PLATFORM}") add_subdirectory(src) diff --git a/pythonfmu3/tests/test_osutil.py b/pythonfmu3/tests/test_osutil.py new file mode 100644 index 0000000..9dcd5bc --- /dev/null +++ b/pythonfmu3/tests/test_osutil.py @@ -0,0 +1,23 @@ +import pytest + +from pythonfmu3 import osutil + + +@pytest.mark.parametrize( + "system, machine, is_64bit, expected", + [ + ("Darwin", "arm64", True, "aarch64-darwin"), # Apple Silicon + ("Darwin", "x86_64", True, "x86_64-darwin"), # Intel Mac (or Rosetta) + ("Linux", "x86_64", True, "x86_64-linux"), + ("Linux", "aarch64", True, "aarch64-linux"), # Raspberry Pi 4/5, Graviton + ("Linux", "armv7l", False, "aarch32-linux"), + ("Windows", "AMD64", True, "x86_64-windows"), + ("Windows", "AMD64", False, "x86-windows"), # 32-bit Python on 64-bit Windows + ("Windows", "ARM64", True, "aarch64-windows"), + ], +) +def test_fmi3_platform_tuple(monkeypatch, system, machine, is_64bit, expected): + monkeypatch.setattr(osutil.platform, "system", lambda: system) + monkeypatch.setattr(osutil.platform, "machine", lambda: machine) + monkeypatch.setattr(osutil, "_is_64bit", lambda: is_64bit) + assert osutil.get_platform() == expected