Actions: Migrate to newer versions of macos #477
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: MHKiT-MATLAB Unix Unit Tests | |
| # Only run tests when MATLAB files or this workflow file changes | |
| on: | |
| push: | |
| branches: [ master, develop ] | |
| paths: | |
| - 'mhkit/**/*.m' | |
| - 'examples/**/*.m' | |
| - '.github/workflows/unix_unit_tests.yml' | |
| pull_request: | |
| branches: [ master, develop ] | |
| paths: | |
| - 'mhkit/**/*.m' | |
| - 'examples/**/*.m' | |
| - '.github/workflows/unix_unit_tests.yml' | |
| jobs: | |
| cache_population: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: ["ubuntu-22.04"] | |
| python-version: ["3.11"] | |
| matlab-version: [R2023b] | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| mhkit-python-dir: "MHKiT-Python" | |
| MHKIT_PYTHON_VERSION: '1.0.1' | |
| steps: | |
| - name: Install & Setup Miniconda | |
| uses: conda-incubator/setup-miniconda@v3 | |
| with: | |
| miniconda-version: latest | |
| auto-update-conda: true | |
| python-version: ${{ matrix.python-version }} | |
| activate-environment: mhkit_conda_env | |
| channels: conda-forge,defaults | |
| # - name: "Conda install netcdf4, hdf5" | |
| # shell: bash -l {0} | |
| # run: | | |
| # conda activate mhkit_conda_env | |
| # conda install numpy cython pip pytest hdf5 libnetcdf cftime netcdf4 | |
| # OutOfProcess mode runs Python in a separate process, so LD_PRELOAD | |
| # can force system libstdc++ for Python without affecting MATLAB's internals | |
| - name: Fix libstdc++ compatibility | |
| if: matrix.os == 'ubuntu-22.04' | |
| run: | | |
| # Force MATLAB to use system libstdc++ instead of its bundled (older) version | |
| # LD_PRELOAD forces the library to be loaded first | |
| # LD_LIBRARY_PATH ensures symbol resolution finds the system library | |
| echo "LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6" >> "$GITHUB_ENV" | |
| echo "LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH" >> "$GITHUB_ENV" | |
| - name: Check out MHKiT-MATLAB | |
| uses: actions/checkout@v4 | |
| # mhkit depends on matplotlib-base, but pandas tries to register a matplotlib | |
| # plotting backend during mhkit import. matplotlib-base may not be sufficient | |
| # for this registration, so we install full matplotlib explicitly. | |
| - name: Conda install mhkit | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| conda install -c conda-forge mhkit==$MHKIT_PYTHON_VERSION matplotlib | |
| - name: Verify matplotlib and mhkit imports | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| python -c "import matplotlib; print(f'matplotlib: {matplotlib.__version__}')" | |
| python -c "import mhkit; print(f'mhkit: {mhkit.__version__}')" | |
| - name: pip install mhkit-python-utils module from source | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| pip install -e . | |
| - name: Pin scipy version | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| pip uninstall -y scipy | |
| pip install scipy==1.14.0 | |
| - name: List installed pip modules | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| pip freeze | |
| # Create the cache and add a dummy file | |
| # The dummy file ensures that the artifact download | |
| - name: Setup mhkit_webread_cache | |
| shell: bash -l {0} | |
| run: | | |
| mkdir mhkit_webread_cache | |
| touch mhkit_webread_cache/test.txt | |
| echo "Hello World" > mhkit_webread_cache/test.txt | |
| - name: Set up MATLAB | |
| uses: matlab-actions/setup-matlab@v2 | |
| with: | |
| release: ${{ matrix.matlab-version }} | |
| # Generate run.m using Python to match Windows workflow approach | |
| # OutOfProcess mode with LD_PRELOAD allows Python to use system libstdc++ | |
| - name: Generate run.m | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| python -c " | |
| import sys | |
| python_exe = sys.executable | |
| content = f'''pyenv(Version='{python_exe}', ExecutionMode='OutOfProcess') | |
| % Verify Python environment | |
| pe = pyenv; | |
| disp(pe); | |
| % Warm up numpy - must call function to force resolution | |
| py.importlib.import_module('numpy'); | |
| dummy = py.numpy.array([1,2,3]); | |
| disp(class(dummy)); | |
| clear dummy; | |
| disp('py.numpy.array: resolved'); | |
| % Warm up mhkit submodules - force resolution by calling functions | |
| py.importlib.import_module('mhkit'); | |
| py.importlib.import_module('mhkit.wave.io.ndbc'); | |
| py.importlib.import_module('mhkit.wave.io.swan'); | |
| py.importlib.import_module('mhkit.river.resource'); | |
| disp('mhkit submodules: imported'); | |
| version | |
| pyenv | |
| addpath(genpath('mhkit')) | |
| import matlab.unittest.TestSuite | |
| import matlab.unittest.TestRunner | |
| testFolder = ['mhkit' filesep 'tests']; | |
| suite = TestSuite.fromFolder(testFolder); | |
| runner = TestRunner.withTextOutput; | |
| results = runner.run(suite); | |
| assertSuccess(results) | |
| ''' | |
| with open('run.m', 'w', encoding='utf-8', newline='\\n') as f: | |
| f.write(content) | |
| " | |
| - name: Output run.m | |
| shell: bash | |
| run: cat run.m | |
| - name: Run MATLAB Unit Tests | |
| uses: matlab-actions/run-command@v2 | |
| with: | |
| command: run | |
| startup-options: -noFigureWindows | |
| - name: Save mhkit_webread_cache directory as an artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| # GitHub Action "Name" of artifact | |
| name: mhkit_webread_cache | |
| # Filesystem path of directory | |
| path: mhkit_webread_cache | |
| main: | |
| needs: cache_population | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [macos-15, macos-15-intel, "ubuntu-22.04"] | |
| python-version: ["3.10", 3.11, 3.12] | |
| # Note: It is preferred to use an actual release name as opposed to 'latest' | |
| # macos-15 is ARM (Apple Silicon) - R2023b+ run natively | |
| # macos-15-intel is x64 - R2022b/R2023a run natively (no Rosetta needed) | |
| matlab-version: [R2022b, R2023a, R2023b, R2024a, R2024b, R2025a] | |
| mhkit-python-version: ["1.0.1"] | |
| exclude: | |
| # Python 3.12 + MATLAB incompatibility | |
| - matlab-version: R2024a | |
| python-version: 3.12 | |
| - matlab-version: R2023b | |
| python-version: 3.12 | |
| - matlab-version: R2023a | |
| python-version: 3.12 | |
| - matlab-version: R2022b | |
| python-version: 3.12 | |
| - matlab-version: R2025a | |
| python-version: 3.12 | |
| # Python 3.11 + MATLAB incompatibility | |
| - matlab-version: R2023a | |
| python-version: 3.11 | |
| - matlab-version: R2022b | |
| python-version: 3.11 | |
| # Architecture-specific macOS exclusions | |
| # Intel MATLAB (R2022b, R2023a) should only run on macos-15-intel | |
| - matlab-version: R2022b | |
| os: macos-15 | |
| - matlab-version: R2023a | |
| os: macos-15 | |
| # ARM MATLAB (R2023b+) should only run on macos-15 (ARM) | |
| - matlab-version: R2023b | |
| os: macos-15-intel | |
| - matlab-version: R2024a | |
| os: macos-15-intel | |
| - matlab-version: R2024b | |
| os: macos-15-intel | |
| - matlab-version: R2025a | |
| os: macos-15-intel | |
| # Specific MATLAB/Python/OS Errors | |
| # Error using _cythonized_array_utils>init | |
| # scipy.linalg._cythonized_array_utils (line 1) | |
| # Python Error: ValueError: numpy.dtype size changed, may indicate binary | |
| # incompatibility. Expected 96 from C header, got 88 from PyObject | |
| # - matlab-version: R2024b | |
| # python-version: 3.12 | |
| # os: macos-15 | |
| # Unable to resolve the name 'py.mhkit.wave.resource.jonswap_spectrum' | |
| # - matlab-version: R2023a | |
| # python-version: 3.10 | |
| # os: "ubuntu-22.04" | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| mhkit-python-dir: "MHKiT-Python" | |
| MHKIT_PYTHON_VERSION: ${{ matrix.mhkit-python-version }} | |
| steps: | |
| - name: Check out MHKiT-MATLAB | |
| uses: actions/checkout@v4 | |
| - name: Install & Setup Miniconda | |
| uses: conda-incubator/setup-miniconda@v3 | |
| with: | |
| miniconda-version: latest | |
| auto-update-conda: true | |
| python-version: ${{ matrix.python-version }} | |
| activate-environment: mhkit_conda_env | |
| channels: conda-forge,defaults | |
| # This is necessary to fix any issues with netcdf4 and hdf5 headers | |
| - name: "Conda install netcdf4, hdf5" | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| conda install numpy cython pip pytest hdf5 libnetcdf cftime netcdf4 | |
| - name: Fix libstdc++ compatibility | |
| if: matrix.os == 'ubuntu-22.04' | |
| run: | | |
| # Force MATLAB to use system libstdc++ instead of its bundled (older) version | |
| # LD_PRELOAD forces the library to be loaded first | |
| # LD_LIBRARY_PATH ensures symbol resolution finds the system library | |
| echo "LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6" >> "$GITHUB_ENV" | |
| echo "LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH" >> "$GITHUB_ENV" | |
| # Pin compatible ICU version based on MATLAB's bundled libstdc++ capabilities | |
| # Older MATLAB versions bundle older libstdc++ missing newer GLIBCXX symbols | |
| - name: Pin compatible ICU version for MATLAB | |
| if: matrix.os == 'ubuntu-22.04' | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| # Lookup table: MATLAB version -> max compatible ICU version | |
| # Based on GLIBCXX symbols available in MATLAB's bundled libstdc++ | |
| declare -A ICU_COMPAT=( | |
| ["R2022a"]="70" | |
| ["R2022b"]="70" | |
| ["R2023a"]="70" | |
| ["R2023b"]="" # empty = use latest (has modern libstdc++) | |
| ["R2024a"]="" | |
| ["R2024b"]="" | |
| ["R2025a"]="" | |
| ) | |
| MATLAB_VERSION="${{ matrix.matlab-version }}" | |
| ICU_VERSION="${ICU_COMPAT[$MATLAB_VERSION]}" | |
| if [ -n "$ICU_VERSION" ]; then | |
| echo "MATLAB $MATLAB_VERSION requires older ICU. Installing icu=$ICU_VERSION" | |
| conda install -y "icu<=$ICU_VERSION" | |
| else | |
| echo "MATLAB $MATLAB_VERSION has modern libstdc++, using default ICU" | |
| fi | |
| # Diagnostic: Check what GLIBC symbols are required by key libraries | |
| - name: Diagnose library dependencies (Ubuntu) | |
| if: matrix.os == 'ubuntu-22.04' | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| echo "=== System libstdc++ GLIBCXX versions ===" | |
| strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX | tail -10 | |
| echo "" | |
| echo "=== Conda libicuuc location and GLIBCXX requirements ===" | |
| ICUUC=$(find $CONDA_PREFIX -name "libicuuc.so*" 2>/dev/null | head -1) | |
| if [ -n "$ICUUC" ]; then | |
| echo "Found: $ICUUC" | |
| strings "$ICUUC" | grep GLIBCXX || echo "No GLIBCXX requirements" | |
| else | |
| echo "libicuuc not found in conda env" | |
| fi | |
| echo "" | |
| echo "=== MATLAB libstdc++ location (if accessible) ===" | |
| ls -la /opt/hostedtoolcache/MATLAB/*/x64/sys/os/glnxa64/libstdc++.so.6 2>/dev/null || echo "MATLAB libstdc++ not found" | |
| - name: Print Python executable | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| python -c "import sys; print(sys.executable)" | |
| - name: Print Python Version | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| python --version | |
| - name: pip install mhkit from pypi | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| export PATH="${CONDA_PREFIX}/bin:${CONDA_PREFIX}/Library/bin:$PATH" # so setup.py finds nc-config | |
| pip install mhkit==$MHKIT_PYTHON_VERSION | |
| - name: pip upgrade netcdf4 | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| export PATH="${CONDA_PREFIX}/bin:${CONDA_PREFIX}/Library/bin:$PATH" # so setup.py finds nc-config | |
| pip install --upgrade netcdf4 | |
| - name: pip install mhkit-python-utils module from source | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| pip install -e . | |
| - name: Pin scipy version | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| pip uninstall -y scipy | |
| pip install scipy==1.14.0 | |
| - name: List installed pip modules | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| pip freeze | |
| - name: Print MHKiT-Python Version | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| python -c "import mhkit; print(mhkit.__version__)" | |
| - name: Verify MHKiT-Python Operation | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| python -c "import mhkit; [ED, AP] = mhkit.river.performance.circular(30); print(ED); print(AP);" | |
| - name: Download mhkit_webread_cache artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: mhkit_webread_cache | |
| path: mhkit_webread_cache | |
| - name: Display structure mhkit_webread_cache | |
| run: ls -R | |
| working-directory: . | |
| - name: Install Amazon Corretto 11 for ARM MATLAB | |
| if: runner.os == 'macOS' | |
| run: | | |
| # ARM MATLAB (R2023b+) requires Java 11 | |
| brew install --cask corretto@11 | |
| echo "JAVA_HOME=$(/usr/libexec/java_home -v 11)" >> $GITHUB_ENV | |
| - name: Set up MATLAB | |
| uses: matlab-actions/setup-matlab@v2 | |
| with: | |
| release: ${{ matrix.matlab-version }} | |
| - name: Configure OpenSSL for MacOS/MATLAB | |
| if: runner.os == 'macOS' | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| conda install openssl=="3.0.*" | |
| CONDA_LIB_PATH=$(python -c "import sys; import os; print(os.path.join(os.path.dirname(sys.executable), 'lib'))") | |
| export DYLD_LIBRARY_PATH="$CONDA_LIB_PATH:$DYLD_LIBRARY_PATH" | |
| echo "DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH" >> $GITHUB_ENV | |
| - name: Add Python Dir to Path | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| printf "getenv('path')\nsetenv('path', ['%s;', getenv('path')])\ngetenv('path')\n" $(python -c "import sys; import os; print(os.path.dirname(sys.executable))") >> run.m | |
| # OutOfProcess is required to properly call netcdf/hdf5 and works reliably on macos and ubuntu | |
| # Note: LD_PRELOAD and LD_LIBRARY_PATH are set via GITHUB_ENV earlier for Linux | |
| - name: Configure MATLAB Python Environment | |
| shell: bash -l {0} | |
| run: | | |
| conda activate mhkit_conda_env | |
| printf 'pyenv(Version="%s", ExecutionMode="OutOfProcess")\n' $(python -c "import sys; print(sys.executable)") >> run.m | |
| # Pass dynamic library paths to MATLAB only on macOS | |
| if [[ $OSTYPE == "darwin"* ]]; then | |
| echo "setenv('DYLD_LIBRARY_PATH', getenv('DYLD_LIBRARY_PATH'));" >> run.m | |
| fi | |
| # Warm up Python OutOfProcess worker to prevent intermittent "Unable to resolve" errors | |
| - name: Add Python environment verification | |
| shell: bash | |
| run: | | |
| echo "% Verify Python environment and warm up OutOfProcess worker" >> run.m | |
| echo "pe = pyenv;" >> run.m | |
| echo "disp(pe);" >> run.m | |
| echo "py.importlib.import_module('numpy');" >> run.m | |
| echo "py.importlib.import_module('mhkit');" >> run.m | |
| echo "disp('Python environment verified: numpy and mhkit imported successfully');" >> run.m | |
| - name: Add MATLAB test commands | |
| shell: bash | |
| run: echo "version, | |
| addpath(genpath('mhkit')), | |
| import matlab.unittest.TestSuite, | |
| import matlab.unittest.TestRunner, | |
| testFolder = ['mhkit' filesep 'tests'], | |
| suite = TestSuite.fromFolder(testFolder), | |
| runner = TestRunner.withTextOutput, | |
| results = runner.run(suite), | |
| assertSuccess(results)" >> run.m | |
| - name: Output run.m | |
| shell: bash | |
| run: cat run.m | |
| # This is a good idea but does not work because you cannot explicitly set the python execution mode | |
| # - name: Run MHKiT-MATLAB Unit Tests | |
| # uses: matlab-actions/run-tests@v1 | |
| # with: | |
| # select-by-folder: mhkit/tests | |
| - name: Run MATLAB Unit Tests | |
| uses: matlab-actions/run-command@v2 | |
| with: | |
| command: run | |
| startup-options: -noFigureWindows |