Very simple CI workflow for Fortran apps using Conda

Hi, in my repo I have created a very very simple CI for testing portability across OS for a very simple Fortran project with common dependencies for scientific applications.

I use a single conda environment and a simple demo workflow that simply builds the environment and tests the compilation across Windows, Linux, and MacOs.

This is heavily inspired by the post on using Conda for Fortran compiler portability.

The next step is extend the matrix to test using the FPM and multiple compilers. The objective of this repo (besides what the readme says) is to create a small template project for Fortran applications that use MPI, BLAS, CMake, etc. plus the FPM :slight_smile:

MPI on Windows seems to be a nightmare, that’s why there’s no MPI supported at the moment…

If anyone has experience with conda and mpi on Windows, I am all ears.

3 Likes

you can install fpm though conda as well conda install conda-forge::fpm it is kept up-to-date so you can get the latest version :wink:

On windows your best bet would be using intel mpi conda install -c https://software.repos.intel.com/python/conda/ -c conda-forge impi_rt impi-devel.

I just did that and got it working with fpm, will add it to the CI tomorrow (it’s 11PM in Australia) :slight_smile:

I will also try the intel MPI, see what happens ! thanks so much for the help

:slight_smile: I was also inspired by Degenerate Conic | Conda + Fortran which is similar to what you’re working on. However, I’m facing two issues, the first one is with ifx on Windows.

Run fpm test --compiler ifx --profile debug
  
 + mkdir build\dependencies
[  0%]                      test1.f90
[ 50%]                      test1.f90  done.
[ 50%]                      test1.exe
[100%]                      test1.exe  done.
Intel(R) Fortran Compiler for applications running on Intel(R) 64, Version 2025.1.0 Build 20250317
<ERROR> Compilation failed for object " test1.exe "
<ERROR> stopping due to failed compilation
Copyright (C) 1985-2025 Intel Corporation. All rights reserved.
STOP 1
ifx: command line warning #10161: unrecognized source type 'build\ifx_958EEB88C884AC4D\test\test_test1.f90.o'; object file assumed
link: unknown option -- d
Try 'link --help' for more information.
Error: Process completed with exit code 1.

And the second issue is with flang on Windows:

Run fpm test --compiler flang-new --profile debug
 + mkdir build\dependencies
[  0%]                      test1.f90
[ 50%]                      test1.f90  done.

flang-new: error: unsupported option '-fPIC' for target 'x86_64-pc-windows-msvc'
<ERROR> Compilation failed for object " test_test1.f90.o "
flang-new: error: unsupported option '-fPIC' for target 'x86_64-pc-windows-msvc'
<ERROR> stopping due to failed compilation
STOP 1
Error: Process completed with exit code 1.

All other compilers work without any problems. Here are the sources:

ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ${{ matrix.os }}
    defaults:
      run:
        shell: bash -el {0}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        compiler: [gfortran, ifx, lfortran, flang-new]
        exclude:
          - os: macos-latest
            compiler: flang-new
          - os: macos-latest
            compiler: ifx
          - os: windows-latest
            compiler: lfortran

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Conda
        uses: conda-incubator/setup-miniconda@v3
        with:
          activate-environment: fortran
          channels: conda-forge, defaults

      - name: Install setup-fortran-conda dependencies
        run: npm install
        working-directory: ./.github/actions/setup-fortran-conda

      - name: Setup Fortran environment
        uses: ./.github/actions/setup-fortran-conda
        with:
          compiler: ${{ matrix.compiler }}
          platform: ${{ matrix.os }}

      - name: Verify installations
        run: |
          fpm --version
          # ${{ matrix.compiler }} --version

      - name: fpm test (debug)
        run: fpm test --compiler ${{ matrix.compiler }} --profile debug

      - name: fpm test (release)
        run: fpm test --compiler ${{ matrix.compiler }} --profile release
index.js
const core = require('@actions/core');
const exec = require('@actions/exec');

async function execCondaCommand_lin(command, env = {}) {
    return await execCommand_lin(`. $CONDA/etc/profile.d/conda.sh && conda activate fortran && ${command}`, env);
}

async function execCondaCommand_win(command, env = {}) {
    return await execCommand_win(`. $CONDA/etc/profile.d/conda.sh && conda activate fortran && ${command}`, env);
}

async function execCondaCommand_mac(command, env = {}) {
    return await execCommand_mac(`. $CONDA/etc/profile.d/conda.sh && conda activate fortran && ${command}`, env);
}


async function execCommand_lin(command, env = {}) {
    return await execCommand(command, '/bin/bash', env);
}

async function execCommand_win(command, env = {}) {
    return await execCommand(command, 'bash.exe', env);
}

async function execCommand_mac(command, env = {}) {
    return await execCommand(command, '/bin/bash', env);
}


async function execCommand(command, shell, env = {}) {
    let output = '';
    const options = {
        listeners: {
            stdout: (data) => { output += data.toString(); },
            stderr: (data) => { output += data.toString(); }
        },
        env: { ...process.env, ...env }
    };

    const exitCode = await exec.exec(shell, ['-c', command], options);
    if (exitCode !== 0) {
        core.setFailed(`Command failed: ${command}\nOutput: ${output}`);
    }

    return output;
}

async function install_fpm_lin() {
    core.info('Installing fpm on Linux...');
    await execCondaCommand_lin('conda install -y -c conda-forge fpm');
}

async function install_fpm_win() {
    core.info('Installing fpm on Windows...');
    await execCondaCommand_win('conda install -y -c conda-forge fpm');
}

async function install_fpm_mac() {
    core.info('Installing fpm on macOS...');
    await execCondaCommand_mac('conda install -y -c conda-forge fpm');
}


async function install_fpm(platform) {
    const installFunctions = {
        'ubuntu-latest': install_fpm_lin,
        'windows-latest': install_fpm_win,
        'macos-latest': install_fpm_mac
    };

    if (installFunctions[platform]) {
        await installFunctions[platform]();
    } else {
        core.warning(`No fpm installation method defined for ${platform}.`);
    }
}


async function install_gfortran_lin() {
    core.info('Installing gfortran on Linux...');
    await execCondaCommand_lin('conda install -y -c conda-forge gfortran_linux-64');
}

async function install_gfortran_win() {
    core.info('Installing gfortran on Windows...');
    await execCondaCommand_win('conda install -y -c conda-forge gfortran_win-64');
}

async function install_gfortran_mac() {
    core.info('Installing gfortran on macOS...');
    await execCondaCommand_mac('conda install -y -c conda-forge gfortran_osx-64');
}


async function install_ifx_lin() {
    core.info('Installing IFX on Linux...');
    await execCondaCommand_lin('conda install -y -c conda-forge ifx_linux-64');
}

async function install_ifx_win() {
    core.info('Installing IFX on Windows...');
    await execCondaCommand_win('conda install -y -c conda-forge ifx_win-64');
}

async function install_ifx_mac() {
    core.warning('IFX is not available on macOS via Conda.');
}


async function install_flang_lin() {
    core.info('Installing Flang on Linux...');
    await execCondaCommand_lin('conda install -y -c conda-forge flang');
}

async function install_flang_win() {
    core.info('Installing Flang on Windows...');
    await execCondaCommand_win('conda install -y -c conda-forge flang');
}

async function install_flang_mac() {
    core.warning('Flang is not available on macOS via Conda.');
}


async function install_lfortran_lin() {
    core.info('Installing LFortran on Linux...');
    await execCondaCommand_lin('conda install -y -c conda-forge lfortran');
}

async function install_lfortran_win() {
    core.info('Installing LFortran on Windows...');
    await execCondaCommand_win('conda install -y -c conda-forge lfortran');
}

async function install_lfortran_mac() {
    core.info('Installing LFortran on macOS...');
    await execCondaCommand_mac('conda install -y -c conda-forge lfortran');
}


async function install_packages_lin(packages) {
    core.info(`Installing additional packages on Linux: ${packages}`);
    await execCondaCommand_lin(`conda install -y -c conda-forge ${packages}`);
}

async function install_packages_win(packages) {
    core.info(`Installing additional packages on Windows: ${packages}`);
    await execCondaCommand_win(`conda install -y -c conda-forge ${packages}`);
}

async function install_packages_mac(packages) {
    core.info(`Installing additional packages on macOS: ${packages}`);
    await execCondaCommand_mac(`conda install -y -c conda-forge ${packages}`);
}


async function install_compiler(compiler, platform) {
    const installFunctions = {
        'gfortran': {
            'ubuntu-latest': install_gfortran_lin,
            'windows-latest': install_gfortran_win,
            'macos-latest': install_gfortran_mac
        },
        'ifx': {
            'ubuntu-latest': install_ifx_lin,
            'windows-latest': install_ifx_win,
            'macos-latest': install_ifx_mac
        },
        'flang-new': {
            'ubuntu-latest': install_flang_lin,
            'windows-latest': install_flang_win,
            'macos-latest': install_flang_mac
        },
        'lfortran': {
            'ubuntu-latest': install_lfortran_lin,
            'windows-latest': install_lfortran_win,
            'macos-latest': install_lfortran_mac
        }
    };

    if (installFunctions[compiler] && installFunctions[compiler][platform]) {
        await installFunctions[compiler][platform]();
    } else {
        core.warning(`No installation method defined for ${compiler} on ${platform}.`);
    }
}


async function install_packages(platform, packages) {
    const installFunctions = {
        'ubuntu-latest': install_packages_lin,
        'windows-latest': install_packages_win,
        'macos-latest': install_packages_mac
    };

    if (packages.trim()) {
        if (installFunctions[platform]) {
            await installFunctions[platform](packages);
        } else {
            core.warning(`No package installation method defined for ${platform}.`);
        }
    }
}


async function configure_runtime_environment(compiler, platform) {
    if (platform === 'ubuntu-latest') {
        core.info(`Configuring runtime environment for ${compiler} on Linux...`);

        let condaPrefix = '';
        await exec.exec('conda', ['info', '--base'], {
            listeners: {
                stdout: (data) => {
                    condaPrefix += data.toString();
                }
            }
        });

        condaPrefix = condaPrefix.trim();

        if (!condaPrefix) {
            core.setFailed('Failed to determine CONDA_PREFIX. Conda might not be properly configured.');
            return;
        }

        const ldLibraryPath = `${condaPrefix}/envs/fortran/lib:${process.env.LD_LIBRARY_PATH || ''}`;

        core.exportVariable('LD_LIBRARY_PATH', ldLibraryPath);
        core.info(`LD_LIBRARY_PATH set to ${ldLibraryPath}`);
    }
}


async function run() {
    try {
        const compiler = core.getInput('compiler');
        const platform = core.getInput('platform');
        const packages = core.getInput('packages');

        core.info('Starting Fortran setup action...');

        await install_fpm(platform);
        await install_compiler(compiler, platform);
        await install_packages(platform, packages);

        await configure_runtime_environment(compiler, platform);

        core.info('Fortran setup action completed successfully.');

    } catch (error) {
        core.setFailed(error.message);
    }
}

run();

[edited:] I’ve now created a repo for this: GitHub - gha3mi/setup-fortran-conda

2 Likes