Skip to content

fix: link Fortran executables with --coverage under code coverage - #235

Merged
f0rmiga merged 1 commit into
mainfrom
fortran-coverage-link
Jul 27, 2026
Merged

fix: link Fortran executables with --coverage under code coverage#235
f0rmiga merged 1 commit into
mainfrom
fortran-coverage-link

Conversation

@f0rmiga

@f0rmiga f0rmiga commented Jul 27, 2026

Copy link
Copy Markdown
Owner

A fortran_binary that statically links coverage-instrumented C/C++ objects fails to link under bazel coverage:

libxwin.a(Xwin2.pic.o): in function `_sub_I_00100_0':
Xwin2.c:(.text.startup._sub_I_00100_0+0x8): undefined reference to `__gcov_init'
Xwin2.c:(.text.exit._sub_D_00100_1+0x1): undefined reference to `__gcov_exit'
Xwin2.c:(.data.rel..LPBX0+0x20): undefined reference to `__gcov_merge_add'
collect2: error: ld returned 1 exit status

Cause

Bazel enables the coverage feature on every action whenever code coverage is being collected (configure_features, via ctx.configuration.coverage_enabled). This toolchain does not set no_legacy_features, so rules_cc injects the legacy coverage / gcc_coverage_map_format features — and those attach --coverage only to the standard C/C++ link actions (cpp_link_executable, cpp_link_dynamic_library, cpp_link_nodeps_dynamic_library, lto_index_for_*).

fortran-link-executable is a custom action name defined by this repo, so no rules_cc feature enumerates it. _fortran_binary_impl builds its link line from cc_common.get_memory_inefficient_command_line(action_name = fortran_link_executable, ...), which therefore contributes nothing coverage-related. The C half of the link is instrumented and emits __gcov_* references, so the link fails.

The asymmetry only shows up in the target configuration — the same binaries link fine in the exec configuration, where coverage is off, so a Fortran binary used purely as a genrule tool never trips it.

Fix

A fortran_coverage feature that mirrors --coverage onto fortran-link-executable, gated with with_feature_set(features = ["coverage"]) so non-coverage builds are byte-identical to before.

This lives in the toolchain rather than in _fortran_binary_impl because every other Fortran link flag here is already expressed as a feature (fortran_link_flags, static_libgfortran, linker-lld, the sanitizers), which keeps the flag out of the rule implementation and lets a consumer opt out per target with features = ["-fortran_coverage"].

Why link-only

Instrumenting fortran-compile as well would additionally require rules_fortran to declare the .gcno outputs and report them through InstrumentedFilesInfo. Without that, -ftest-coverage emits undeclared outputs that Bazel discards — cost with no usable data — so Fortran sources deliberately stay uninstrumented. Doing it properly is a larger, separate change.

Test

tests/coverage/ adds a fortran_binary linking an instrumented C cc_library, plus a build_test. It is not manual: without coverage it is an ordinary mixed Fortran/C link check in the regular suite, and under --collect_code_coverage it exercises the fix.

The existing coverage CI job only ran //examples/hello_world_cpp:hello_world_cpp_test — pure C++ — which is why this gap survived. It now also builds //tests/coverage/....

Verification

  • With the fix reverted, bazel test --collect_code_coverage //tests/coverage/... reproduces the exact __gcov_init / __gcov_exit / __gcov_merge_add failure; with it, the test passes.
  • The instrumented path is genuinely exercised, not trivially skipped: libadd_one.a carries 5 __gcov references, add_one.pic.gcno is emitted, and the linked binary runs.
  • aquery confirms --coverage appears on the FortranLink command line under coverage and is absent without it.
  • bazel test //... passes (12/12), as does the exact command the coverage CI job runs.
  • Downstream check on a large mixed Fortran/C++/Python monorepo: 43 test targets that previously could not build under bazel coverage (blocked by AVL and XFOIL, both fortran_binary targets linking an instrumented xwin C library) now build and pass, and a combined LCOV report is produced.

Bazel enables the "coverage" feature on every action when code coverage
is being collected, but rules_cc's legacy gcc_coverage_map_format
feature only attaches --coverage to the C/C++ link actions. A
fortran_binary that statically links coverage-instrumented C/C++ objects
therefore failed to link:

    libxwin.a(Xwin2.pic.o): undefined reference to `__gcov_init'
    libxwin.a(Xwin2.pic.o): undefined reference to `__gcov_exit'
    libxwin.a(Xwin2.pic.o): undefined reference to `__gcov_merge_add'

Add a fortran_coverage feature that mirrors --coverage onto
fortran-link-executable, gated on the coverage feature so non-coverage
builds are unchanged.

Only the link action is covered. Instrumenting fortran-compile would
also require rules_fortran to declare the .gcno outputs and report them
through InstrumentedFilesInfo; without that, -ftest-coverage emits
undeclared outputs that Bazel discards, so Fortran sources stay
uninstrumented.

The coverage CI job only exercised a C++ test, which is why this went
unnoticed. It now also builds tests/coverage, where a fortran_binary
links an instrumented C library; that target fails to link without this
change.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes bazel coverage link failures for mixed Fortran/C(/C++) builds by ensuring the Fortran link action also receives coverage link flags when Bazel enables the coverage feature during coverage collection.

Changes:

  • Adds a fortran_coverage toolchain feature that mirrors --coverage onto the fortran-link-executable action when the coverage feature is enabled.
  • Introduces a coverage regression test that links a fortran_binary against a coverage-instrumented cc_library.
  • Expands the CI coverage workflow to include the new //tests/coverage/... targets.

Reviewed changes

Copilot reviewed 2 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
toolchain/cc_toolchain_config.bzl Adds a fortran_coverage feature to apply --coverage to the Fortran link action under Bazel coverage.
tests/coverage/main.f90 Adds a minimal Fortran program calling a C function via iso_c_binding to validate mixed-language linking.
tests/coverage/BUILD.bazel Defines the mixed Fortran/C targets and a build_test used by coverage CI to exercise the link behavior.
tests/coverage/add_one.c Adds a small C function intended to be coverage-instrumented and linked into the Fortran executable.
.github/workflows/default.yaml Updates the coverage job to run coverage over //tests/coverage/... in addition to the existing C++ example test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@f0rmiga
f0rmiga merged commit c065c22 into main Jul 27, 2026
31 checks passed
@f0rmiga
f0rmiga deleted the fortran-coverage-link branch July 27, 2026 21:12
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.

2 participants