diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b48c3a98..6b4f1e7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,9 @@ jobs: - name: x86_64-darwin image: macos-13 arch: x86_64 + - name: universal64-darwin + image: macos-13 + arch: x86_64 - name: aarch64-darwin image: macos-13 arch: aarch64 @@ -91,6 +94,10 @@ jobs: with: name: x86_64-darwin path: dist-x86_64-darwin + - uses: actions/download-artifact@v4 + with: + name: universal64-darwin + path: dist-universal64-darwin - uses: actions/download-artifact@v4 with: name: aarch64-darwin diff --git a/CMakeLists.txt b/CMakeLists.txt index 6db01f90..cd2adf62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,8 @@ else () if ("${FMI_ARCHITECTURE}" STREQUAL "x86_64") set (FMI_PLATFORM ${FMI_PLATFORM}64) + elseif ("${FMI_ARCHITECTURE}" STREQUAL "universal64") + set (FMI_PLATFORM ${FMI_PLATFORM}64) elseif ("${FMI_ARCHITECTURE}" STREQUAL "x86") set (FMI_PLATFORM ${FMI_PLATFORM}32) else () @@ -85,6 +87,8 @@ if (APPLE) set(CMAKE_OSX_ARCHITECTURES "x86_64") elseif ("${FMI_ARCHITECTURE}" STREQUAL "aarch64") set(CMAKE_OSX_ARCHITECTURES "arm64") + elseif ("${FMI_ARCHITECTURE}" STREQUAL "universal64") + set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") else () message(FATAL_ERROR "Unsupported architecture darwin: ${FMI_ARCHITECTURE}") endif () diff --git a/build/build.py b/build/build.py index 02760f9c..3fa8a22c 100644 --- a/build/build.py +++ b/build/build.py @@ -12,7 +12,7 @@ parser = argparse.ArgumentParser() parser.add_argument( 'platform', - choices={'x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin'}, + choices={'x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin', 'universal64-darwin'}, help="Platform to build for, e.g. x86_64-windows" ) parser.add_argument( @@ -91,6 +91,9 @@ def build_fmus(fmi_version, fmi_type=None): cmake_args += ['-D', 'CMAKE_OSX_ARCHITECTURES=arm64'] + elif fmi_platform == 'universal64-darwin': + cmake_args += ['-D', 'CMAKE_OSX_ARCHITECTURES=arm64;x86_64'] + install_dir = build_dir / 'install' if fmi_type is not None: @@ -135,9 +138,9 @@ def build_fmus(fmi_version, fmi_type=None): if __name__ == '__main__': - if args.platform in {'x86_64-linux', 'x86-windows', 'x86_64-windows', 'x86_64-darwin'}: + if args.platform in {'x86_64-linux', 'x86-windows', 'x86_64-windows', 'x86_64-darwin', 'universal64-darwin'}: build_fmus(fmi_version=1, fmi_type='me') build_fmus(fmi_version=1, fmi_type='cs') build_fmus(fmi_version=2) - - build_fmus(fmi_version=3) + if args.platform not in {'universal64-darwin'}: + build_fmus(fmi_version=3) diff --git a/build/build_cvode.py b/build/build_cvode.py index 8449821f..ceb12c89 100644 --- a/build/build_cvode.py +++ b/build/build_cvode.py @@ -8,7 +8,7 @@ parser = argparse.ArgumentParser() parser.add_argument( 'platform', - choices={'x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin'}, + choices={'x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin', 'universal64-darwin'}, help="Platform to build for, e.g. x86_64-windows" ) parser.add_argument( @@ -62,6 +62,10 @@ cmake_args += ['-D', 'CMAKE_OSX_ARCHITECTURES=arm64'] +elif fmi_platform == 'universal64-darwin': + + cmake_args += ['-D', 'CMAKE_OSX_ARCHITECTURES=arm64;x86_64'] + check_call( ['cmake'] + cmake_args + diff --git a/build/build_libxml2.py b/build/build_libxml2.py index 38c5b4fb..8315ff80 100644 --- a/build/build_libxml2.py +++ b/build/build_libxml2.py @@ -8,7 +8,7 @@ parser = argparse.ArgumentParser() parser.add_argument( 'platform', - choices={'x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin'}, + choices={'x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin', 'universal64-darwin'}, help="Platform to build for, e.g. x86_64-windows" ) parser.add_argument( @@ -60,6 +60,10 @@ cmake_args += ['-D', 'CMAKE_OSX_ARCHITECTURES=arm64'] +elif fmi_platform == 'universal64-darwin': + + cmake_args += ['-D', 'CMAKE_OSX_ARCHITECTURES=arm64;x86_64'] + check_call( ['cmake'] + cmake_args + diff --git a/build/build_zlib.py b/build/build_zlib.py index 7d942148..e82dd61d 100644 --- a/build/build_zlib.py +++ b/build/build_zlib.py @@ -7,7 +7,7 @@ parser = argparse.ArgumentParser() parser.add_argument( 'platform', - choices={'x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin'}, + choices={'x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin', 'universal64-darwin'}, help="Platform to build for, e.g. x86_64-windows" ) parser.add_argument( @@ -63,6 +63,10 @@ cmake_args += ['-D', 'CMAKE_OSX_ARCHITECTURES=arm64'] +elif fmi_platform == 'universal64-darwin': + + cmake_args += ['-D', 'CMAKE_OSX_ARCHITECTURES=arm64;x86_64'] + check_call( ['cmake'] + cmake_args + diff --git a/build/merge_binaries.py b/build/merge_binaries.py index db9c128f..c1ef62f9 100644 --- a/build/merge_binaries.py +++ b/build/merge_binaries.py @@ -94,10 +94,10 @@ def merge_fmus(version): tempdir = Path(mkdtemp()) - platforms = ['x86-windows', 'x86_64-windows', 'x86_64-linux', 'x86_64-darwin'] - if version == '3.0': - platforms += ['aarch64-linux', 'aarch64-darwin'] + platforms = ['x86-windows', 'x86_64-windows', 'x86_64-linux', 'x86_64-darwin', 'aarch64-linux', 'aarch64-darwin'] + else: + platforms = ['x86-windows', 'x86_64-windows', 'x86_64-linux', 'universal64-darwin'] for platform in platforms: @@ -179,7 +179,7 @@ def get_unit(variable): os.makedirs(results_dir, exist_ok=True) # copy fmusim -for system in ['x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin']: +for system in ['x86-windows', 'x86_64-windows', 'x86_64-linux', 'aarch64-linux', 'x86_64-darwin', 'aarch64-darwin', 'universal64-darwin']: shutil.copytree(src=root / f'dist-{system}' / f'fmusim-{system}', dst=root / 'dist-merged' / f'fmusim-{system}') # copy license and readme diff --git a/tests/test_fmusim.py b/tests/test_fmusim.py index a4778936..0727921e 100644 --- a/tests/test_fmusim.py +++ b/tests/test_fmusim.py @@ -44,7 +44,8 @@ def call_fmusim(platform: str, fmi_version: int, interface_type: str, test_name: @pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) def test_start_time(fmi_version, interface_type, arch, platform): - if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") result = call_fmusim(platform, fmi_version, interface_type, 'test_start_time', ['--start-time', '0.5']) @@ -55,7 +56,8 @@ def test_start_time(fmi_version, interface_type, arch, platform): @pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) def test_stop_time(fmi_version, interface_type, arch, platform): - if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") result = call_fmusim(platform, fmi_version, interface_type, 'test_stop_time', ['--stop-time', '1.5']) @@ -66,7 +68,8 @@ def test_stop_time(fmi_version, interface_type, arch, platform): @pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) def test_set_stop_time(fmi_version, interface_type, arch, platform): - if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") result = call_fmusim(platform, fmi_version, interface_type, 'test_set_stop_time', @@ -83,7 +86,8 @@ def test_set_stop_time(fmi_version, interface_type, arch, platform): @pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) def test_start_value_types(fmi_version, interface_type, arch, platform): - if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") args = [ @@ -134,7 +138,9 @@ def test_start_value_types(fmi_version, interface_type, arch, platform): @pytest.mark.parametrize('interface_type', ['cs', 'me']) -def test_start_value_arrays(work_dir, interface_type, platform): +def test_start_value_arrays(work_dir, interface_type, arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") call_fmusim( platform=platform, @@ -164,7 +170,8 @@ def test_start_value_arrays(work_dir, interface_type, platform): @pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) def test_input_file(fmi_version, interface_type, arch, platform): - if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") result = call_fmusim( @@ -197,7 +204,9 @@ def test_input_file(fmi_version, interface_type, arch, platform): assert result['Int32_output'][-1] == 2 -def test_arrays(work_dir, platform): +def test_arrays(work_dir, arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") call_fmusim( platform=platform, @@ -227,7 +236,9 @@ def test_arrays(work_dir, platform): ''' -def test_collapsed_array(work_dir, platform): +def test_collapsed_array(work_dir, arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") call_fmusim( platform=platform, @@ -258,7 +269,8 @@ def test_collapsed_array(work_dir, platform): @pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) def test_fmi_log_file(fmi_version, interface_type, arch, platform): - if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") fmi_log_file = work / f'test_fmi_log_file_fmi{fmi_version}_{interface_type}.txt' @@ -277,7 +289,8 @@ def test_fmi_log_file(fmi_version, interface_type, arch, platform): @pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) def test_output_interval(fmi_version, interface_type, arch, platform): - if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") result = call_fmusim( @@ -297,7 +310,8 @@ def test_output_interval(fmi_version, interface_type, arch, platform): @pytest.mark.parametrize('fmi_version, solver', product([1, 2, 3], ['euler', 'cvode'])) def test_solver(fmi_version, solver, arch, platform): - if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") call_fmusim( @@ -312,7 +326,8 @@ def test_solver(fmi_version, solver, arch, platform): @pytest.mark.parametrize('fmi_version, interface_type', product([1, 2, 3], ['cs', 'me'])) def test_output_variable(fmi_version, interface_type, arch, platform): - if fmi_version in {1, 2} and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") result = call_fmusim( @@ -326,7 +341,9 @@ def test_output_variable(fmi_version, interface_type, arch, platform): assert set(result.dtype.names) == {'time', 'e', 'der(h)'} -def test_intermediate_values(platform): +def test_intermediate_values(arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") result = call_fmusim( platform=platform, @@ -339,7 +356,9 @@ def test_intermediate_values(platform): assert np.all(np.diff(result['time']) < 0.1) -def test_early_return_state_events(platform): +def test_early_return_state_events(arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") result = call_fmusim( platform=platform, @@ -354,7 +373,9 @@ def test_early_return_state_events(platform): assert np.sum(np.logical_and(time > 0, time < 0.5)) == 1 -def test_event_mode_input_events(platform): +def test_event_mode_input_events(arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") input_file = resources / 'Feedthrough_in.csv' @@ -378,7 +399,9 @@ def test_event_mode_input_events(platform): assert np.all(result['Int32_output'] == [1, 1, 1, 1, 1, 1, 1, 2, 2]) -def test_event_mode_time_events(platform): +def test_event_mode_time_events(arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") result = call_fmusim( platform=platform, @@ -401,7 +424,8 @@ def test_event_mode_time_events(platform): @pytest.mark.parametrize('fmi_version, interface_type', product([2, 3], ['cs', 'me'])) def test_restore_fmu_state(fmi_version, interface_type, arch, platform): - if fmi_version == 2 and arch not in {'x86', 'x86_64'}: + if (fmi_version in {1, 2} and arch not in {'x86', 'x86_64', 'universal64'}) or \ + (fmi_version in {3} and arch in {'universal64'}): pytest.skip(f"FMI version {fmi_version} is not supported on {arch}.") result1 = call_fmusim( diff --git a/tests/test_targets.py b/tests/test_targets.py index 2463f779..398c40e3 100644 --- a/tests/test_targets.py +++ b/tests/test_targets.py @@ -43,7 +43,7 @@ def validate(build_dir, model, fmi_types, simulate=True): def test_fmi1_me(arch, platform): - if arch not in {'x86', 'x86_64'}: + if arch not in {'x86', 'x86_64', 'universal64'}: pytest.skip(f"{arch} not supported") build_dir = root / 'build' / f'fmi1-me-{platform}' @@ -59,7 +59,7 @@ def test_fmi1_me(arch, platform): def test_fmi1_cs(arch, platform): - if arch not in {'x86', 'x86_64'}: + if arch not in {'x86', 'x86_64', 'universal'}: pytest.skip(f"{arch} not supported") build_dir = root / 'build' / f'fmi1-cs-{platform}' @@ -75,7 +75,7 @@ def test_fmi1_cs(arch, platform): def test_fmi2(arch, platform): - if arch not in {'x86', 'x86_64'}: + if arch not in {'x86', 'x86_64', 'universal64'}: pytest.skip(f"{arch} not supported") build_dir = root / 'build' / f'fmi2-{platform}' @@ -93,6 +93,9 @@ def test_fmi2(arch, platform): def test_fmi3(arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") + build_dir = root / 'build' / f'fmi3-{platform}' models = ['BouncingBall', 'Dahlquist', 'Feedthrough', 'Resource', 'Stair', 'StateSpace', 'VanDerPol'] @@ -108,15 +111,23 @@ def test_fmi3(arch, platform): assert not validate_fmu(build_dir / 'install' / 'Clocks.fmu') -def test_cs_early_return(platform): +def test_cs_early_return(arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") + run_example(root / 'build' / f'fmi3-{platform}' / 'temp' / 'cs_early_return') -def test_cs_event_mode(platform): +def test_cs_event_mode(arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") + run_example(root / 'build' / f'fmi3-{platform}' / 'temp' / 'cs_event_mode') -def test_cs_reconfiguration(platform): +def test_cs_reconfiguration(arch, platform): + if arch in {'universal64'}: + pytest.skip(f"FMI version 3 is not supported on {arch}.") build_dir = root / 'build' / f'fmi3-{platform}' / 'temp'