diff --git a/.github/actions/build-gha-docker-image/action.yml b/.github/actions/build-gha-docker-image/action.yml new file mode 100644 index 0000000..505182b --- /dev/null +++ b/.github/actions/build-gha-docker-image/action.yml @@ -0,0 +1,65 @@ +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions +# https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-docker-images#publishing-images-to-github-packages +name: 'Build docker image for use in GitHub Actions' +description: 'Builds a docker image for use in GitHub Actions for a given compiler and a given compiler version.' + +inputs: + compiler-name: + description: 'The Fortran compiler to install.' + required: true + compiler-version: + description: 'The compiler version to install.' + required: true + docker-registry-name: + description: 'The docker registry to publish to.' + default: 'ghcr.io' + docker-registry-username: + description: 'The username to login to the docker registry with.' + required: true + docker-registry-password: + description: 'The password to login to the docker registry with.' + required: true + image-name: + description: 'The name of the docker image.' + required: true + +runs: + using: 'composite' + steps: + - name: "Log in to the container registry" + uses: docker/login-action@v3 + with: + registry: ${{ inputs.docker-registry-name }} + username: ${{ inputs.docker-registry-username }} + password: ${{ inputs.docker-registry-password }} + + - name: 'Construct long-form docker tag' + id: long-form-tag + shell: bash + run: | + echo "tag=type=raw,value=linux-${{ inputs.compiler-name }}-${{ inputs.compiler-version }}-pr-${{ github.event.number }}" >> "${GITHUB_OUTPUT}" + + - name: "Extract metadata (tags, labels) for docker" + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.docker-registry-name }}/${{ inputs.image-name }} + tags: | + ${{ steps.long-form-tag.outputs.tag }} + type=raw,value=${{ inputs.compiler-name }}-${{ inputs.compiler-version }} + type=sha + type=ref,enable=true,priority=600,prefix=,suffix=,event=branch + type=ref,enable=true,priority=600,prefix=,suffix=,event=tag + type=ref,enable=true,priority=600,prefix=pr-,suffix=,event=pr + + - name: "Build and push docker image" + id: push + uses: docker/build-push-action@v6 + with: + context: docker/github-actions/ + build-args: | + COMPILER=${{ inputs.compiler-name }} + VERSION=${{ inputs.compiler-version }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 0000000..d723dd9 --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,38 @@ +name: 'Test setup-fortran ran correctly' +description: 'Checks the system after setup-fortran has run.' + +inputs: + compiler-name: + description: 'The Fortran compiler installed.' + required: true + compiler-version: + description: 'The compiler version installed.' + required: true + output-mode: + description: 'The output mode of CI.' + required: true + +runs: + using: 'composite' + steps: + - name: 'Test Fortran compiler' + uses: ./.github/actions/test-fc + with: + compiler: ${{ inputs.compiler-name }} + version: ${{ inputs.compiler-version }} + + - name: 'Test C compiler' + continue-on-error: true + if: inputs.output-mode == 'report' + uses: ./.github/actions/test-cc + with: + compiler: ${{ inputs.compiler-name }} + version: ${{ inputs.compiler-version }} + + - name: 'Test C++ compiler' + continue-on-error: true + if: inputs.output-mode == 'report' + uses: ./.github/actions/test-cxx + with: + compiler: ${{ inputs.compiler-name }} + version: ${{ inputs.compiler-version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4648aa1..2f5c671 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,162 +54,210 @@ jobs: # debugging - name: Show matrix run: echo "${{ toJSON(fromJSON(steps.matrix.outputs.matrix)) }}" - - test: - name: Test - needs: options - runs-on: ${{ matrix.os }} - continue-on-error: ${{ needs.options.outputs.mode == 'report' }} + + + build-gha-docker-images: + name: 'Build and push GHA docker image' + runs-on: ubuntu-latest + timeout-minutes: 12 + permissions: + contents: read + packages: write + attestations: write + id-token: write strategy: fail-fast: false - matrix: ${{ fromJSON(needs.options.outputs.matrix) }} + matrix: + include: + - compiler-name: 'gcc' + compiler-version: '13' steps: - - name: Checkout repository + - name: 'Checkout repository' uses: actions/checkout@v4 - - name: Setup Fortran - id: setup-fortran - continue-on-error: ${{ needs.options.outputs.mode == 'report' }} - uses: ./ - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - # - name: Debug with tmate - # uses: mxschmitt/action-tmate@v3 + - name: 'Copy setup-fortran to docker directory' + run: cp main.sh setup-fortran.sh docker/github-actions/ - - name: Test Fortran compiler - if: steps.setup-fortran.outcome == 'success' - uses: ./.github/actions/test-fc - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - - name: Test C compiler - continue-on-error: true - if: needs.options.outputs.mode == 'report' && steps.setup-fortran.outcome == 'success' - uses: ./.github/actions/test-cc - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - - name: Test C++ compiler - continue-on-error: true - if: needs.options.outputs.mode == 'report' && steps.setup-fortran.outcome == 'success' - uses: ./.github/actions/test-cxx + - name: 'Build and push docker image' + uses: ./.github/actions/build-gha-docker-image with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} + compiler-name: ${{ matrix.compiler-name }} + compiler-version: ${{ matrix.compiler-version }} + docker-registry-name: 'ghcr.io' + docker-registry-username: ${{ github.actor }} + docker-registry-password: ${{ secrets.GITHUB_TOKEN }} + image-name: ${{ github.repository }} - - name: Create compatibility report - if: needs.options.outputs.mode == 'report' - shell: bash - run: | - mkdir -p compat - support=$([ "${{ steps.setup-fortran.outcome }}" == "success" ] && echo "✓" || echo "") - prefix="${{ matrix.os }},${{ matrix.toolchain.compiler }},${{ matrix.toolchain.version }}" - report="compat/${prefix//,/_}.csv" - echo "$prefix,$support" >> "$report" - cat "$report" - - - name: Upload compatibility report - if: needs.options.outputs.mode == 'report' - uses: actions/upload-artifact@v4 - with: - name: compat-${{ matrix.os }}-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }} - path: compat/*.csv - - compat: - name: Report compatibility - needs: + test-gha-docker-images: + name: 'Test GHA docker image' + needs: - options - - test - if: needs.options.outputs.mode == 'report' + - build-gha-docker-images + continue-on-error: ${{ needs.options.outputs.mode == 'report' }} runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + include: + - compiler-name: 'gcc' + compiler-version: '13' + container: + image: ghcr.io/${{ github.repository }}:linux-${{ matrix.compiler-name }}-${{ matrix.compiler-version }}-pr-${{ github.event.number }} steps: - - - name: Checkout repository + - name: 'Checkout repository' uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: 3.9 + - name: 'Setup environment' + run: /etc/github-actions/update-job-environment.sh - - name: Install packages - run: pip install -r requirements.txt - - - name: Download reports - uses: actions/download-artifact@v4 - with: - pattern: compat-* - path: compat - merge-multiple: true - - - name: Concatenate reports - run: | - echo "runner,compiler,version,support" > .github/compat/long_compat.csv - cat compat/*.csv >> .github/compat/long_compat.csv - - - name: Make wide reports - working-directory: .github/compat - run: | - python wide_compat_reports.py "long_compat.csv" "compat.csv" - cat compat.md - - - name: Upload artifacts - uses: actions/upload-artifact@v4 + - name: 'Test the installed compilers' + uses: ./.github/actions/test with: - name: compat - path: | - .github/compat/long_compat.csv - .github/compat/compat.csv - .github/compat/compat.md - - - name: Check for changes - working-directory: .github/compat - id: diff - run: | - if ! [ -f compat.csv ]; then - echo "diff=false" >> $GITHUB_OUTPUT - exit 0 - fi - - diff=$(git diff compat.csv) - if [[ $diff == "" ]]; then - echo "No changes found" - echo "diff=false" >> $GITHUB_OUTPUT - else - echo "Changes found:" - echo "$diff" - echo "diff=true" >> $GITHUB_OUTPUT - fi - - - name: Update README - if: ${{ steps.diff.outputs.diff == 'true' }} - run: python .github/compat/update_compat_table.py ".github/compat/compat.md" "README.md" - - - name: Print README diff - if: ${{ steps.diff.outputs.diff == 'true' }} - run: git diff README.md - - - name: Create pull request - if: ${{ steps.diff.outputs.diff == 'true' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - now=$(date +'%Y-%m-%dT%H-%M-%S') - updated_branch="compat_$now" - default_branch="${{ github.event.repository.default_branch }}" - - git switch -c "$updated_branch" - git add .github/compat/*compat.csv README.md - git commit -m "Update compatibility matrix" - git push -u origin "$updated_branch" - gh pr create -B "$default_branch" -H "$updated_branch" --title "Update compatibility matrix" --body-file .github/compat/compat.md + compiler-name: ${{ matrix.compiler-name }} + compiler-version: ${{ matrix.compiler-version }} + output-mode: ${{ needs.options.outputs.mode }} +# +# test: +# name: Test +# needs: options +# runs-on: ${{ matrix.os }} +# continue-on-error: ${{ needs.options.outputs.mode == 'report' }} +# strategy: +# fail-fast: false +# matrix: ${{ fromJSON(needs.options.outputs.matrix) }} +# steps: +# - name: Checkout repository +# uses: actions/checkout@v4 +# +# - name: Setup Fortran +# id: setup-fortran +# continue-on-error: ${{ needs.options.outputs.mode == 'report' }} +# uses: ./ +# with: +# compiler: ${{ matrix.toolchain.compiler }} +# version: ${{ matrix.toolchain.version }} +# +# # - name: Debug with tmate +# # uses: mxschmitt/action-tmate@v3 +# +# - name: 'Test the installed compilers' +# if: steps.setup-fortran.outcome == 'success' +# uses: ./.github/actions/test +# with: +# compiler-name: ${{ matrix.toolchain.compiler }} +# compiler-version: ${{ matrix.toolchain.version }} +# output-mode: ${{ needs.options.outputs.mode }} +# +# - name: Create compatibility report +# if: needs.options.outputs.mode == 'report' +# shell: bash +# run: | +# mkdir -p compat +# support=$([ "${{ steps.setup-fortran.outcome }}" == "success" ] && echo "✓" || echo "") +# prefix="${{ matrix.os }},${{ matrix.toolchain.compiler }},${{ matrix.toolchain.version }}" +# report="compat/${prefix//,/_}.csv" +# echo "$prefix,$support" >> "$report" +# cat "$report" +# +# - name: Upload compatibility report +# if: needs.options.outputs.mode == 'report' +# uses: actions/upload-artifact@v4 +# with: +# name: compat-${{ matrix.os }}-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }} +# path: compat/*.csv +# +# compat: +# name: Report compatibility +# needs: +# - options +# - test +# if: needs.options.outputs.mode == 'report' +# runs-on: ubuntu-latest +# permissions: +# contents: write +# pull-requests: write +# steps: +# +# - name: Checkout repository +# uses: actions/checkout@v4 +# +# - name: Setup Python +# uses: actions/setup-python@v4 +# with: +# python-version: 3.9 +# +# - name: Install packages +# run: pip install -r requirements.txt +# +# - name: Download reports +# uses: actions/download-artifact@v4 +# with: +# pattern: compat-* +# path: compat +# merge-multiple: true +# +# - name: Concatenate reports +# run: | +# echo "runner,compiler,version,support" > .github/compat/long_compat.csv +# cat compat/*.csv >> .github/compat/long_compat.csv +# +# - name: Make wide reports +# working-directory: .github/compat +# run: | +# python wide_compat_reports.py "long_compat.csv" "compat.csv" +# cat compat.md +# +# - name: Upload artifacts +# uses: actions/upload-artifact@v4 +# with: +# name: compat +# path: | +# .github/compat/long_compat.csv +# .github/compat/compat.csv +# .github/compat/compat.md +# +# - name: Check for changes +# working-directory: .github/compat +# id: diff +# run: | +# if ! [ -f compat.csv ]; then +# echo "diff=false" >> $GITHUB_OUTPUT +# exit 0 +# fi +# +# diff=$(git diff compat.csv) +# if [[ $diff == "" ]]; then +# echo "No changes found" +# echo "diff=false" >> $GITHUB_OUTPUT +# else +# echo "Changes found:" +# echo "$diff" +# echo "diff=true" >> $GITHUB_OUTPUT +# fi +# +# - name: Update README +# if: ${{ steps.diff.outputs.diff == 'true' }} +# run: python .github/compat/update_compat_table.py ".github/compat/compat.md" "README.md" +# +# - name: Print README diff +# if: ${{ steps.diff.outputs.diff == 'true' }} +# run: git diff README.md +# +# - name: Create pull request +# if: ${{ steps.diff.outputs.diff == 'true' }} +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# run: | +# git config user.name "github-actions[bot]" +# git config user.email "41898282+github-actions[bot]@users.noreply.github.com" +# +# now=$(date +'%Y-%m-%dT%H-%M-%S') +# updated_branch="compat_$now" +# default_branch="${{ github.event.repository.default_branch }}" +# +# git switch -c "$updated_branch" +# git add .github/compat/*compat.csv README.md +# git commit -m "Update compatibility matrix" +# git push -u origin "$updated_branch" +# gh pr create -B "$default_branch" -H "$updated_branch" --title "Update compatibility matrix" --body-file .github/compat/compat.md diff --git a/docker/github-actions/Dockerfile b/docker/github-actions/Dockerfile new file mode 100644 index 0000000..5b63cdd --- /dev/null +++ b/docker/github-actions/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:24.04 + +ARG COMPILER +ARG VERSION + +# Update dependencies. +RUN apt update && apt -y upgrade + +# Create a directory for the GITHUB_* files +RUN mkdir -p /etc/github-actions + +# Copy in the script for use when running this container, to populate the runtime environment. +COPY --chmod=755 ./update-job-environment.sh /etc/github-actions/update-job-environment.sh + +# Install the specified Fortran compiler. +COPY ./main.sh ./setup-fortran.sh /tmp/ +COPY --chmod=755 ./run-setup-fortran.sh /tmp/run-setup-fortran.sh +RUN env GITHUB_ENV=/etc/github-actions/ENV GITHUB_PATH=/etc/github-actions/PATH /tmp/run-setup-fortran.sh diff --git a/docker/github-actions/run-setup-fortran.sh b/docker/github-actions/run-setup-fortran.sh new file mode 100755 index 0000000..140f5a6 --- /dev/null +++ b/docker/github-actions/run-setup-fortran.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +# Install necessary dependencies. +apt install -y make software-properties-common sudo + +# Run setup-fortran. +bash /tmp/main.sh diff --git a/docker/github-actions/update-job-environment.sh b/docker/github-actions/update-job-environment.sh new file mode 100755 index 0000000..ead0705 --- /dev/null +++ b/docker/github-actions/update-job-environment.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# This script is to be run within a GitHub Action job step, and is used to update the runtime +# environment variables based on environment variables setup during build time. +set -ue + +D=/etc/github-actions + +for v in ENV PATH; do + if [ -r ${D}/${v} ]; then + env_var=GITHUB_${v} + if env | grep -q "^${env_var}="; then + cat ${D}/${v} >> ${!env_var} + fi + fi +done diff --git a/main.sh b/main.sh index 1fcf76f..76fd131 100644 --- a/main.sh +++ b/main.sh @@ -9,7 +9,7 @@ if [[ "$RUNNER_OS" == "macOS" ]] && [[ "$compiler" == "intel" ]]; then compiler="intel-classic" fi -source ./setup-fortran.sh +source $(dirname ${0})/setup-fortran.sh case $compiler in gcc) diff --git a/setup-fortran.sh b/setup-fortran.sh index a2247f9..e6b988c 100755 --- a/setup-fortran.sh +++ b/setup-fortran.sh @@ -2,6 +2,14 @@ set -ex +export_environment_variable() +{ + # Export the environment variable into the current shell. + export $1=$2 + # Export the environment variable for future shells. + echo "${1}=${2}" >> "${GITHUB_ENV}" +} + require_fetch() { if command -v curl > /dev/null 2>&1; then @@ -153,26 +161,24 @@ install_gcc() ;; esac - export FC="gfortran" - export CC="gcc" - export CXX="g++" + export_environment_variable FC 'gfortran' + export_environment_variable CC 'gcc' + export_environment_variable CXX 'g++' } export_intel_vars() { - cat >> $GITHUB_ENV <> $GITHUB_PATH done @@ -524,13 +530,13 @@ install_intel() esac if $classic; then - export FC="ifort" - export CC="icc" - export CXX="icpc" + export_environment_variable FC "ifort" + export_environment_variable CC "icc" + export_environment_variable CXX "icpc" else - export FC="ifx" - export CC="icx" - export CXX="icpx" + export_environment_variable FC "ifx" + export_environment_variable CC "icx" + export_environment_variable CXX "icpx" fi } @@ -574,8 +580,8 @@ install_nvidiahpc_apt() # load NVIDIA HPC SDK module echo "Loading NVIDIA HPC SDK $version module..." - NVCOMPILERS=/opt/nvidia/hpc_sdk; export NVCOMPILERS - export MODULEPATH=$NVCOMPILERS/modulefiles:$MODULEPATH + export_environment_variable NVCOMPILERS "/opt/nvidia/hpc_sdk" + export_environment_variable MODULEPATH "$NVCOMPILERS/modulefiles:$MODULEPATH" module load nvhpc echo "NVIDIA HPC SDK $version module loaded." @@ -612,36 +618,36 @@ install_nvidiahpc() ;; esac - export FC="nvfortran" - export CC="nvc" - export CXX="nvc++" + export_environment_variable FC "nvfortran" + export_environment_variable CC "nvc" + export_environment_variable CXX "nvc++" } install_lfortran_l() { local version=$1 - export CC="gcc" - export CXX="g++" - export CONDA=conda + export_environment_variable CC "gcc" + export_environment_variable CXX "g++" + export_environment_variable CONDA "conda" $CONDA install -c conda-forge -n base -y lfortran=$version } install_lfortran_w() { local version=$1 - export CC="cl" - export CXX="cl" - export CONDA=$CONDA\\Scripts\\conda # https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md#environment-variables + export_environment_variable CC "cl" + export_environment_variable CXX "cl" + export_environment_variable CONDA "$CONDA\\Scripts\\conda" # https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md#environment-variables $CONDA install -c conda-forge -n base -y lfortran=$version } install_lfortran_m() { local version=$1 - export CC="gcc" - export CXX="g++" - export CONDA_ROOT_PREFIX=$MAMBA_ROOT_PREFIX - export CONDA=micromamba + export_environment_variable CC "gcc" + export_environment_variable CXX "g++" + export_environment_variable CONDA_ROOT_PREFIX "$MAMBA_ROOT_PREFIX" + export_environment_variable CONDA "micromamba" $CONDA install -c conda-forge -n base -y lfortran=$version } @@ -671,5 +677,5 @@ install_lfortran() esac echo $($CONDA run -n base which lfortran | sed 's/lfortran//') >> $GITHUB_PATH - export FC="lfortran" + export_environment_variable FC "lfortran" }