Skip to content

feat: have cmake backend#1251

Open
JeffIrwin wants to merge 95 commits intofortran-lang:mainfrom
JeffIrwin:generate_cmake
Open

feat: have cmake backend#1251
JeffIrwin wants to merge 95 commits intofortran-lang:mainfrom
JeffIrwin:generate_cmake

Conversation

@JeffIrwin
Copy link
Copy Markdown

@JeffIrwin JeffIrwin commented Feb 21, 2026

Summary

Adds fpm generate --cmake command to export fpm projects as CMake build files, enabling CMake-based builds for better IDE integration and deployment to environments without fpm.

Features

  • Generated CMakeLists.txt files for library, executable, and test targets
  • Dependency handling with automatic subdirectory inclusion
  • Full language support for Fortran (free/fixed form), C, and C++
  • Preprocessing with custom suffixes and per-dependency macros
  • Metapackage integration (MPI, OpenMP, BLAS, HDF5, NetCDF, stdlib, etc.)
  • Cross-platform tested on Linux, macOS, and Windows CI
  • Staleness detection warns when generated CMake files are out of sync
    • Staleness warning has some false-positives
    • Any change that causes fpm build to re-build will also cause a staleness warning
    • Unfortunetaly, I think the only robust way to check cmake without false-positives is to entirely regenerate cmake in memory and compare it to the existing file. I fear that this is too expensive to do on every run of fpm build, fpm test, and fpm run

Test Coverage

  • Unit tests for core generation logic
  • Integration tests covering ~70 example packages
  • Self-hosting test: fpm builds itself using generated CMake

Performance

Optimized with hash tables for O(n) dependency resolution and source lookups.

Fixes #1240

JeffIrwin and others added 30 commits January 19, 2026 11:24
Add a new subcommand that generates a CMakeLists.txt file from an fpm
project. This allows building fpm projects with CMake for better IDE
integration or deployment to environments where fpm is not available.

The generated CMakeLists.txt includes:
- Library target (if src/ sources exist)
- Executable targets (from app/)
- Test targets with enable_testing() and add_test()

Limitations documented in help text:
- fpm dependencies not auto-handled in CMake
- Metapackages/registry deps not translated
- Complex preprocessing may need adjustment

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, `get_sources_for_exe` only included sources where `exe_name`
matched the executable name. However, `exe_name` is only set for source
files containing a `program` statement, not for helper modules.

This caused CMake builds to fail for test executables like `fpm-test`
that depend on multiple source files (e.g., main.f90 + testsuite.f90
+ test_*.f90 modules).

The fix changes the logic to:
1. Find the main program file by matching exe_name
2. Extract its directory path
3. Include ALL source files from that directory with the same scope

This matches fpm's native build behavior where all sources in an
executable's source directory are compiled together.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
JeffIrwin and others added 23 commits February 12, 2026 20:04
Previously only detected fpm.toml changes. Now detects source file
additions, deletions, renames, and content changes by hashing source
file paths and content digests already computed by build_model.

Changes:
- Add compute_project_hash() that combines manifest + source digests
- Move staleness check after build_model to access source digests
- Update CMake header from "Manifest hash:" to "Project hash:"
- Maintain backward compatibility with old format (triggers regeneration)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Wrap add_subdirectory calls in if(NOT TARGET) guards to prevent
duplicate target definitions when packages have mutual dependencies.
This allows CMake builds to succeed from either direction in circular
dependency scenarios while maintaining correct behavior for normal
and diamond dependencies.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Tests were only initializing compiler and build_dir fields, leaving other allocatable character fields unallocated. This caused runtime errors on macOS when build_model() passed these unallocated fields to new_compiler() as character(len=*) arguments. Fortran standard requires allocatable actual arguments to be allocated when passed to non-allocatable dummy arguments. macOS gfortran has stricter runtime checks and caught this violation.

Added init_test_settings() helper that properly initializes all fields following the production build_settings() pattern.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
On Windows CI/CD, CMake was defaulting to Visual Studio generators which
cannot find the GCC/gfortran compiler. Detect Windows/MSYS2 environments
and explicitly use the MinGW Makefiles generator for Fortran support.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When fpm generate cmake runs on Windows, it writes source file paths with
Windows-style backslashes (e.g., .\.\src\lib.f) to CMakeLists.txt files.
CMake interprets these backslashes as escape sequences, causing syntax
errors like "Invalid character escape '\s'".

This fix modifies the clean_path() function in cmake.f90 to call unix_path()
before cleaning leading ./ prefixes. The unix_path() function converts all
backslashes to forward slashes, which CMake accepts on all platforms including
Windows.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Extends commit 238c65d to cover include directories, metapackage paths,
and dependency subdirectory paths written to CMakeLists.txt files.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
High-priority fixes:
- Propagate I/O errors from write_lines_to_file instead of silent failure
- Check fpm generate exit code in test_cmake.sh before proceeding to cmake
- Fix integer overflow in hash table using iand() instead of abs()
- Optimize staleness check to read only first 5 lines for non-fpm CMake files

Medium-priority fixes:
- Use preprocessor config name as serialization key instead of positional index
- Propagate errors from generate_dependency_cmake

Low-priority fixes:
- Replace O(n*m) source lookup with O(n) hash map in compute_project_hash

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Extract duplicated compiler-to-cpp-flag mapping into get_cpp_flag(),
remove dead code (unused is_header_only, temp_dirs, FPM_SCOPE_EXAMPLE),
fix portability bug using is_dir() instead of inquire(file=) for dirs,
replace magic number with named project_hash_marker constant, handle
C/C++ header files in language detection, detect dependency languages
from sources instead of hardcoding Fortran, use original test name in
add_test NAME, and fix test_cmake.sh cleanup of dependency files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GNU ld processes arguments left-to-right, so library flags (e.g. MPI's
-lmpi_usempif08) must come after the object files that reference them.
Previously, model%link_flags was prepended before the objects, causing
"undefined reference" errors in metapackage_mpi builds.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
# Conflicts:
#	src/fpm/manifest/dependency.f90
Replaces O(n²) insertion sort + chain-hashing with O(n) XOR-based
hash combination. This eliminates the sort_strings subroutine and
hash table allocations while maintaining deterministic hashing.

Uses XOR to combine per-file hashes (path + digest), which is safe
since file paths are unique. Hash values will change for existing
projects (one-time CMake regeneration cost).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@JeffIrwin JeffIrwin marked this pull request as ready for review February 21, 2026 22:11
@certik
Copy link
Copy Markdown
Member

certik commented Feb 26, 2026

Thanks for working on this, we need this. Is this PR working? Let us know when this is ready for review.

@JeffIrwin
Copy link
Copy Markdown
Author

Hi @certik, yes, this PR is working. Out of 72 example packages in the fpm repo, 71 build and run correctly with generated cmake. Only example_packages/features_with_dependency fails, but it does not work with fpm build either.

Perhaps we should discuss one concern before starting the review. As mentioned in the description, the cmake staleness detection has false positives. There are at least three options:

  1. Revert the staleness detection and save it for a future PR
  2. Accept some false positives in the current implementation
  3. Take the more expensive but robust approach of entirely regenerating CMakeLists.txt in memory and compare against the existing file on every run of fpm build, fpm run, and fpm test

The current implementation uses fpm's digest of every source file to detect cmake staleness. Of course, most small changes to a single source file will not actually require regenerating CMakeLists.txt: https://github.com/JeffIrwin/fpm/blob/5f2eec967e93cdc71fb33eeebd2a83d49dc9c6b6/src/fpm_cmake_check.f90#L16-L18

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Have CMake backend

2 participants