Bug#1149705: dh-fortran: FC_DEFAULT=gfortran resolves to hardcoded gfortran-16 instead of active default gfortran

Viktor Pashaiev w.paszajew at gmail.com
Fri Oct 2 14:48:41 BST 2026


Package: src:dh-fortran
Version: 0.85
Severity: important
Tags: patch
User: ubuntu-devel at lists.ubuntu.com
Usertags: origin-ubuntu stonking ubuntu-patch

Dear Maintainer,

In dhfortran/compilers.py, default_compilers["gfortran"] is hardcoded to "gfortran-16":

  default_compilers = {
      "gfortran": "gfortran-16",
      ...
  }

When debhelper packages or environment settings define FC_DEFAULT=gfortran (e.g. debian/rules or build harnesses), get_fc_default() calls canon("gfortran"):

  def canon(f):
      return default_compilers[f] if f in default_compilers else f

  if "FC_DEFAULT" in os.environ:
      return canon(os.environ["FC_DEFAULT"])

Because "gfortran-16" is not yet available and the system default is gfortran-15 (or whatever gfortran package is installed), get_fc_default() returns "gfortran-16". Subsequent calls fail because which("gfortran-16") returns None (for example in fortran-assert with "Compiler gfortran-16 not on PATH").

To fix this:
1. Dynamically detect the active default compiler from which("gfortran") and resolve symlinks (stripping multiarch / DEB_BUILD_GNU_TYPE prefixes so it matches the compiler dictionary key).
2. Fall back to gfortran-15 if available, else gfortran-16.
3. In canon(f), resolve symlinks when f is in default_compilers so canon("gfortran") maps to the target compiler flavor instead of an uninstalled version.
4. In get_f77(), check which("gfortran") before raising an error.

This issue is also tracked in Ubuntu at:
https://bugs.launchpad.net/ubuntu/+source/dh-fortran/+bug/2169224

The patch below resolves this behavior:

--- a/dhfortran/compilers.py
+++ b/dhfortran/compilers.py
@@ -20,9 +20,23 @@
     check_output(["dpkg-architecture", "-qDEB_HOST_MULTIARCH"]).strip().decode("utf-8")
 )
 
+def _detect_default_compiler(cmd: str, fallback: str) -> str:
+    p = which(cmd)
+    if p is not None:
+        name = Path(p).resolve().name
+        for m in (
+            multiarch,
+            check_output(["dpkg-architecture", "-qDEB_BUILD_GNU_TYPE"]).strip().decode("utf-8"),
+        ):
+            if name.startswith(m + "-"):
+                name = name[len(m) + 1 :]
+        return name
+    return fallback
+
+
 # To be updated as gfortran, flang etc are updated  for new versions
 default_compilers = {
-    "gfortran": "gfortran-16",
+    "gfortran": _detect_default_compiler("gfortran", "gfortran-15" if which("gfortran-15") is not None else "gfortran-16"),
     "flang": "flang-22",
     "flangext": "flang-to-external-fc-18",
 }
@@ -441,7 +455,7 @@
         f = f77 if f77 else "/etc/alternatives/f77"
     fullpath = which(f)
     if fullpath is None:
-        fullpath = which(default_compilers['gfortran'])
+        fullpath = which("gfortran") or which(default_compilers['gfortran'])
     if fullpath is None:
         if f is None:
             f='gfortran' # for better error message
@@ -454,7 +468,22 @@
     """Return the default Fortran compiler"""
 
     def canon(f):
-        return default_compilers[f] if f in default_compilers else f
+        if f in default_compilers:
+            p = which(f)
+            if p is not None:
+                p_name = Path(p).resolve().name
+                for m in (
+                    multiarch,
+                    check_output(["dpkg-architecture", "-qDEB_BUILD_GNU_TYPE"]).strip().decode("utf-8"),
+                ):
+                    if p_name.startswith(m + "-"):
+                        p_name = p_name[len(m) + 1 :]
+                for c in compilers:
+                    if p_name in compilers[c]["exe"]:
+                        return c
+                return p_name
+            return default_compilers[f]
+        return f
     if "FC_DEFAULT" in os.environ:
         return canon(os.environ["FC_DEFAULT"])
     if fc is not None:
--- a/dhfortran/tests/compilers.py
+++ b/dhfortran/tests/compilers.py
@@ -26,6 +26,22 @@
         1, "get_fc_flavor_arch('/bin/garbage') should fail"
 
 
+
+def test_get_fc_default_env():
+    import os
+    orig = os.environ.get("FC_DEFAULT")
+    try:
+        os.environ["FC_DEFAULT"] = "gfortran"
+        cp.get_fc_default.cache_clear()
+        assert cp.get_fc_default() == cp.default_compilers["gfortran"]
+        assert cp.which(cp.get_fc_default()) is not None
+    finally:
+        if orig is not None:
+            os.environ["FC_DEFAULT"] = orig
+        else:
+            os.environ.pop("FC_DEFAULT", None)
+        cp.get_fc_default.cache_clear()
+
 def test_get_fc_default():
     gf = cp.default_compilers["gfortran"]
     assert cp.get_fc_default() == gf



More information about the debian-science-maintainers mailing list