Skip to content

Add macOS testing workflow using gfortran #23

Add macOS testing workflow using gfortran

Add macOS testing workflow using gfortran #23

# Workflow to run the FTorch test suite using jobs for GNU Fortran compiler
# and system Clang compilers (macOS arm64)
name: Test suite (macOS CPU GNU)
# Controls when the workflow will run
on:
# Triggers the workflow on pushes to the "main" branch, i.e., PR merges
push:
branches: [ "main" ]
# Triggers the workflow on pushes to open pull requests with code changes
pull_request:
paths:
# This workflow
- '.github/workflows/test_suite_macos_cpu_gnu.yml'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Cancel jobs running if new commits are pushed
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
# Write permissions are not required
permissions: {}
# Workflow run - one or more jobs that can run sequentially or in parallel
jobs:
# Dynamically build matrix for GNU job
setup-matrix:
name: setup matrix
runs-on: macos-latest
# Terminate the job if it runs for more than 5 minutes
timeout-minutes: 5
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
env:
EVENT_NAME: ${{ github.event_name }}
steps:
- id: set-matrix
run: |
MATRIX_JSON='
{
"std": ["f2008", "f2018"]
}'
# Convert json to compact line expected by github action
MATRIX_JSON=$(echo "$MATRIX_JSON" | jq -c .)
echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT
test-suite:
name: test suite
needs: setup-matrix
# The type of runner that the job will run on
runs-on: macos-latest
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup-matrix.outputs.matrix) }}
# Terminate the job if it runs for more than 20 minutes
timeout-minutes: 20
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout FTorch repository
with:
persist-credentials: true
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.13'
- name: Install PyTorch
run: |
python -m pip install --upgrade pip
python -m venv ftorch
. ftorch/bin/activate
pip install torch torchvision
- name: Install gfortran and OpenMPI
# Install from source to avoid compiler mismatch between gfortran versions
# between open-mpi installation and when building FTorch.
run: |
brew update
brew install gcc openmpi libomp
- name: Ensure gfortran symlink exists
run: |
GFORTRAN_PATH=$(ls $(brew --prefix)/bin/gfortran-* | sort -V | tail -n 1)
if [ ! -e "$(brew --prefix)/bin/gfortran" ]; then
ln -s "$GFORTRAN_PATH" "$(brew --prefix)/bin/gfortran"
fi
ls -l $(brew --prefix)/bin/gfortran*
GCC_PATH=$(ls $(brew --prefix)/bin/gcc-* | grep -E 'gcc-[0-9]+$' | sort -V | tail -n 1)
if [ ! -e "$(brew --prefix)/bin/gcc" ]; then
ln -s "$GCC_PATH" "$(brew --prefix)/bin/gcc"
fi
ls -l $(brew --prefix)/bin/gcc*
opt/homebrew/bin/gcc --version
- name: Set compilers as GitHub environment variables
run: |
# Add homebrew to path
echo "PATH=$(brew --prefix)/bin:$PATH" >> $GITHUB_ENV
# brew specifically installs gcc-<version> so extract this as generic FC.
echo "FC=$(brew --prefix)/bin/gfortran" >> $GITHUB_ENV
echo "CC=$(brew --prefix)/bin/gcc" >> $GITHUB_ENV
echo "CXX=$(brew --prefix)/bin/gcc" >> $GITHUB_ENV
echo "MPI_FC=$(brew --prefix)/bin/mpif90" >> $GITHUB_ENV
- name: Debug - Fortran compiler information
run: |
echo "Checking for gfortran..."
which gfortran|| echo "gfortran not found in PATH"
echo "PATH contents:"
echo $PATH
echo "Checking for any Fortran compilers..."
which gfortran gfortran-13 gfortran-12 gfortran-11 fc || echo "No Fortran compilers found"
if command -v gfortran &> /dev/null; then
echo "gfortran version:"
gfortran --version
fi
echo "Checking for gcc..."
which gcc|| echo "gcc not found in PATH"
echo "PATH contents:"
echo $PATH
echo "Checking for any C compilers..."
which gcc gcc-13 gcc-12 gcc-11 cc || echo "No C compilers found"
if command -v gcc &> /dev/null; then
echo "gcc version:"
$CC --version
fi
- name: Install pFUnit
run: |
# TODO: Avoid version pinning (needed because version appears in install path)
git clone -b v4.12.0 https://github.com/Goddard-Fortran-Ecosystem/pFUnit.git
mkdir pFUnit/build
cd pFUnit/build
cmake ..\
-DCMAKE_C_COMPILER=$CC \
-DCMAKE_Fortran_COMPILER=$FC\
-DMPI_Fortran_COMPILER=$MPI_FC
make -j 4 install
- name: Build FTorch
env:
FORTRAN_STANDARD: ${{ matrix.std }}
run: |
. ftorch/bin/activate
VN=$(python -c "import sys; print('.'.join(sys.version.split('.')[:2]))")
export Torch_DIR=${VIRTUAL_ENV}/lib/python${VN}/site-packages
export BUILD_DIR=$(pwd)/build
# NOTE: The pFUnit version (pinned during installation above) is used in the install path.
export PFUNIT_DIR=$(pwd)/pFUnit/build/installed/PFUNIT-4.12
mkdir ${BUILD_DIR}
cd ${BUILD_DIR}
cmake .. \
-DPython_EXECUTABLE="$(which python)" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=$CC \
-DCMAKE_CXX_COMPILER=$CXX \
-DCMAKE_Fortran_COMPILER=$FC\
-DCMAKE_INSTALL_PREFIX="${BUILD_DIR}" \
-DCMAKE_BUILD_TESTS=TRUE \
-DCMAKE_PREFIX_PATH="${PFUNIT_DIR};${Torch_DIR}" \
-DCMAKE_Fortran_FLAGS="-std=${FORTRAN_STANDARD}"\
-DCMAKE_C_FLAGS="-Xpreprocessor -fopenmp -I$(brew --prefix libomp)/include" \
-DCMAKE_CXX_FLAGS="-Xpreprocessor -fopenmp -I$(brew --prefix libomp)/include" \
-DCMAKE_EXE_LINKER_FLAGS="-L$(brew --prefix libomp)/lib -lomp"
cmake --build .
cmake --install .
- name: Check pkg-config file
run: |
# pkg-config looks in the `build` directory for libraries
export PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:build
libs=$(pkg-config --libs ftorch)
cflags=$(pkg-config --cflags ftorch)
# Assert linker flags not empty
if [[ -z "${libs}" ]]
then
echo "::error ::pkg-config --libs ftorch returned empty output"
exit 1
fi
# Assert expected linker flags
lib_dir=$(grep -oE "\-L.*/lib" <<< ${libs})
lib=$(grep -oE "\-lftorch" <<< ${libs})
if [[ -z "${lib_dir}" || -z "${lib}" ]]
then
echo "::error ::pkg-config --libs ftorch do not contain expected linker flags"
exit 1
fi
# Assert compiler flags not empty
if [[ -z "${cflags}" ]]
then
echo "::error ::pkg-config --cflags ftorch returned empty output"
exit 1
fi
# Assert expected compiler flags
include_dir=$(grep -oE "/include " <<< ${cflags})
module_dir=$(grep -oE "/include/ftorch" <<< ${cflags})
if [[ -z ${include_dir} || -z ${module_dir} ]]
then
echo "::error ::pkg-config --cflags ftorch do not contain expected compiler flags"
exit 1
fi
- name: Run unit tests
run: |
. ftorch/bin/activate
cd build
ctest --verbose --tests-regex unit
- name: Run integration tests
run: |
. ftorch/bin/activate
cd build
ctest --verbose --tests-regex example
# Check that we can successfully build and run an example outside the main FTorch build process
- name: Standalone SimpleNet example
env:
FORTRAN_STANDARD: ${{ matrix.std }}
run: |
. ftorch/bin/activate
VN=$(python -c "import sys; print('.'.join(sys.version.split('.')[:2]))")
export Torch_DIR="${VIRTUAL_ENV}/lib/python${VN}/site-packages/torch"
export FTORCH_BUILD_DIR="$(pwd)/build"
export EXAMPLE_BUILD_DIR="examples/2_SimpleNet/build"
mkdir "${EXAMPLE_BUILD_DIR}"
cd "${EXAMPLE_BUILD_DIR}"
cmake .. \
-DPython_EXECUTABLE="$(which python)" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${FTORCH_BUILD_DIR}" \
-DCMAKE_BUILD_TESTS=TRUE \
-DCMAKE_Fortran_FLAGS="-std=${FORTRAN_STANDARD}"
cmake --build .
ctest -V