[flang-commits] [flang] [llvm] [Flang][CMake] Use fakeflang as dispatcher (PR #209482)

Michael Kruse via flang-commits flang-commits at lists.llvm.org
Tue Jul 14 06:39:33 PDT 2026


https://github.com/Meinersbur created https://github.com/llvm/llvm-project/pull/209482

With #203481 where `runtimes-configure` does not depend on `flang` anymore, the following race could happen in incremental builds: While ninja/make builds some of the libFortran*.so libraries in a BUILD_SHARED_LIBS=ON build, CMake tries to execute flang which tries to load the .so libraries while they are linked. This race condition can only happen in incremental builds because before the real bin/flang is built completely the first time, it points to fakeflang which would be working. In an incremental build real bin/flang has already been built in a previous build and is pointing to currently re-built flang.
I did not think this would be possible because CMake only needs to probe flang once and thereafter remembers the probe result in `CMakeFiles/<version>/CMakeFortranCompiler.cmake`. CMake does not support changing the compiler once configures and errors-out if you try to change `CMAKE_Fortran_COMPILER`. I do not know why/when CMake is re-probing the compiler in some incremental builds, but has been reported in #205360.

This patch avoids any replacement of build artifacts and instead decides in fakeflang when to call real flang or to emulate the Flang preprocessor. The decision is made based of on the FLANG_BOOTSTRAP_PROBE environment variable that is set only in the configure step. The feature that allows this is unfortunately only available starting in CMake 4.2. All suppported CMake versions before CMake 4.2 use the filename `CMakeFortranCompilerId.F`, so make the decision based on that when using earlier versions of CMake.

Fixes #205360


>From 67fc19ed52a03cac504522db768cf626ae300473 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Tue, 14 Jul 2026 11:37:11 +0200
Subject: [PATCH] Use fakeflang as dispatcher

---
 flang/test/Driver/fakeflang.F                 | 28 +++++-
 flang/tools/CMakeLists.txt                    |  8 +-
 flang/tools/fakeflang/CMakeLists.txt          | 21 +----
 .../tools/fakeflang/ensure_flang_exists.cmake | 13 ---
 flang/tools/fakeflang/fakeflang.cpp           | 88 ++++++++++++++-----
 flang/tools/flang-driver/CMakeLists.txt       | 18 ----
 .../modules/LLVMExternalProjectUtils.cmake    |  8 +-
 7 files changed, 104 insertions(+), 80 deletions(-)
 delete mode 100644 flang/tools/fakeflang/ensure_flang_exists.cmake

diff --git a/flang/test/Driver/fakeflang.F b/flang/test/Driver/fakeflang.F
index 526f535769049..1c064b16c323c 100644
--- a/flang/test/Driver/fakeflang.F
+++ b/flang/test/Driver/fakeflang.F
@@ -2,20 +2,38 @@
 
 ! RUN: rm -rf %t
 ! RUN: mkdir -p %t
+! RUN: cp %s %t/CMakeFortranCompilerId.F
+! RUN: cp %s %t/compiletest.F
 ! RUN: cd %t
 
-! RUN: fakeflang -v -c -cpp -E --target=x86_64-unknown-linux-gnu %s
+! RUN: fakeflang -v -c -cpp -E --target=x86_64-unknown-linux-gnu CMakeFortranCompilerId.F 2>&1 | FileCheck %s --check-prefix=FAKEWARNING
 ! RUN: FileCheck %s --input-file=a.out --check-prefixes=CHECK,GNU
 ! RUN: rm a.out
 
-! RUN: fakeflang -v -c -cpp -E --target=aarch64-linux-gnu %s
+! RUN: fakeflang -v -c -cpp -E --target=aarch64-linux-gnu CMakeFortranCompilerId.F 2>&1 | FileCheck %s --check-prefix=FAKEWARNING
 ! RUN: FileCheck %s --input-file=a.out --check-prefixes=CHECK,GNU
 ! RUN: rm a.out
 
-! RUN: fakeflang -v -c -cpp -E --target=x86_64-pc-windows-msvc %s
+! RUN: fakeflang -v -c -cpp -E --target=x86_64-pc-windows-msvc CMakeFortranCompilerId.F 2>&1 | FileCheck %s --check-prefix=FAKEWARNING
 ! RUN: FileCheck %s --input-file=a.out --check-prefixes=CHECK,MSVC --match-full-lines
 ! RUN: rm a.out
 
+! FLANG_BOOTSTRAP_PROBE forces probe mode
+! RUN: env FLANG_BOOTSTRAP_PROBE=1 fakeflang -v -c --target=x86_64-unknown-linux-gnu compiletest.F 2>&1 | FileCheck %s --check-prefix=FAKEWARNING
+! RUN: FileCheck %s --input-file=a.out --check-prefixes=CHECK,GNU
+! RUN: rm a.out
+
+! When not probing, compile everything normally
+! RUN: fakeflang -c compiletest.F -o compiletest.F.o 2>&1 | FileCheck %s --check-prefix=NOFAKE --allow-empty
+! RUN: test -e compiletest.F.o
+
+
+! FAKEWARNING: fakeflang: warning: Emulating flang for compiler probing
+! NOFAKE-NOT: fakeflang
+
+#ifdef __fakeflang_probe__
+! CHECK: dispatcher=1
+dispatcher=__fakeflang_probe__
 
 ! CHECK: CMAKE_Fortran_COMPILER_ID=1
 CMAKE_Fortran_COMPILER_ID=__flang__
@@ -34,3 +52,7 @@
 ! GNU: CMAKE_Fortran_PLATFORM_ID=_WIN32
 ! MSVC: CMAKE_Fortran_PLATFORM_ID=1
 CMAKE_Fortran_PLATFORM_ID=_WIN32
+#else
+      program helloworld
+      end program helloworld
+#endif
diff --git a/flang/tools/CMakeLists.txt b/flang/tools/CMakeLists.txt
index 235a660221820..975eaa29343fc 100644
--- a/flang/tools/CMakeLists.txt
+++ b/flang/tools/CMakeLists.txt
@@ -8,12 +8,8 @@
 
 add_subdirectory(bbc)
 add_subdirectory(flang-driver)
-if (FLANG_STANDALONE_BUILD)
-  # Mock flang only needed in bootstrapping builds;
-  # fall back to full flang otherwise
-  add_custom_target(flang-lazy)
-  add_dependencies(flang-lazy flang)
-else ()
+if (NOT FLANG_STANDALONE_BUILD)
+  # Mock flang only needed in bootstrapping builds
   add_subdirectory(fakeflang)
 endif ()
 add_subdirectory(tco)
diff --git a/flang/tools/fakeflang/CMakeLists.txt b/flang/tools/fakeflang/CMakeLists.txt
index 3503735729494..b760c6efed65c 100644
--- a/flang/tools/fakeflang/CMakeLists.txt
+++ b/flang/tools/fakeflang/CMakeLists.txt
@@ -5,23 +5,8 @@ set(FLANG_BUILD_TOOLS OFF) # Do not install
 add_flang_tool(fakeflang
   fakeflang.cpp
 )
-target_compile_definitions(fakeflang PRIVATE "CMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}")
 
-# Using $<TARGET_FILE:file> in an add_custom_command would automatically
-# add a dependency.
-set(_flang_exe "${LLVM_RUNTIME_OUTPUT_INTDIR}/flang${CMAKE_EXECUTABLE_SUFFIX}")
-
-# Create ${_flang_exe} using fakeflang if it does not exist yet.
-# Cannot use an OUTPUT/BYPRODUCT as it would make ninja complain about multiple
-# rules to create ${_flang_exe} (the other being being the full flang).
-# Automatically execute it after fakeflang has been built instead.
-add_custom_command(POST_BUILD
-  TARGET fakeflang
-  COMMAND "${CMAKE_COMMAND}" "-DINPUT_FILE=$<TARGET_FILE:fakeflang>" "-DOUTPUT_FILE=${_flang_exe}" "-P" "${CMAKE_CURRENT_SOURCE_DIR}/ensure_flang_exists.cmake"
-)
-
-# Phony target that ensures bin/flang exists; if the full flang has not been
-# compiled yet, use a mock flang driver only for passing CMake's
-# CMake_Fortran_COMPILER tests.
+# Phony target that ensures fakeflang can be called to emulate compiler probes.
+# Clang is required to handle the preprocessing.
 add_custom_target(flang-lazy)
-add_dependencies(flang-lazy fakeflang)
+add_dependencies(flang-lazy fakeflang clang)
diff --git a/flang/tools/fakeflang/ensure_flang_exists.cmake b/flang/tools/fakeflang/ensure_flang_exists.cmake
deleted file mode 100644
index 7fef42de6bb0c..0000000000000
--- a/flang/tools/fakeflang/ensure_flang_exists.cmake
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copy INPUT_FILE to OUTPUT_FILE, but only
-# if OUTPUT_FILE does not already exist
-
-if (NOT EXISTS "${OUTPUT_FILE}")
-  cmake_path(GET OUTPUT_FILE PARENT_PATH OUTPUT_DIR)
-  file(MAKE_DIRECTORY "${OUTPUT_DIR}")
-
-  # This could also be the symlink but its small size is not worth the effort
-  # handling platform differences.
-  execute_process(
-    COMMAND ${CMAKE_COMMAND} -E copy "${INPUT_FILE}" "${OUTPUT_FILE}"
-  )
-endif ()
diff --git a/flang/tools/fakeflang/fakeflang.cpp b/flang/tools/fakeflang/fakeflang.cpp
index 563bddde1021d..d23a52296dbb1 100644
--- a/flang/tools/fakeflang/fakeflang.cpp
+++ b/flang/tools/fakeflang/fakeflang.cpp
@@ -25,6 +25,11 @@
 ///
 /// The most relevant preprocessor definition is __flang__ which leads to
 /// CMAKE_Fortran_COMPILER_ID="LLVMFlang".
+///
+/// Whether a particular invocation is CMake's compiler detection is detected
+/// via the filename or when the FLANG_BOOTSTRAP_PROBE environment variable is
+/// set. Any other invocation is forwarded, unmodified, to the real flang
+/// driver that must live right next to this program.
 //
 //===----------------------------------------------------------------------===//
 
@@ -34,14 +39,12 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/InitLLVM.h"
 #include "llvm/Support/Path.h"
+#include "llvm/Support/Process.h"
 #include "llvm/Support/Program.h"
 #include "llvm/Support/WithColor.h"
 #include <cstdint>
 #include <string>
 
-#define STRINGIFY(X) #X
-#define STRINGIFY_EXPANDED(X) STRINGIFY(X)
-
 static std::string getExecutablePath(const char *argv0) {
   void *anchor = (void *)(intptr_t)getExecutablePath;
   return llvm::sys::fs::getMainExecutable(argv0, anchor);
@@ -52,30 +55,63 @@ static std::string getExecutablePath(const char *argv0) {
   exit(EXIT_FAILURE);
 }
 
-int main(int argc, const char **argv) {
-  llvm::InitLLVM X(argc, argv);
-  std::string SelfExe = getExecutablePath(argv[0]);
+/// A CMake compiler detection probe is assumed if
+///
+/// 1. FLANG_BOOTSTRAP_PROBE=1 has been set during runtimes-configure
+///    (requires CMake 4.2+), or
+///
+/// 2. the file being compiled is CMakeFortranCompilerId.F; all supported
+///    CMake versions below 4.2 use this filename.
+static bool isCompilerIdProbe(llvm::ArrayRef<const char *> OrigArgs) {
+  if (llvm::sys::Process::GetEnv("FLANG_BOOTSTRAP_PROBE"))
+    return true;
 
-  if (llvm::sys::path::stem(SelfExe) == "flang") {
-    llvm::WithColor::remark(llvm::errs())
-        << "This is a mock flang compiler; Use '" STRINGIFY_EXPANDED(
-               CMAKE_MAKE_PROGRAM) " flang' to replace it with the real "
-                                   "compiler\n";
-  }
+  return llvm::any_of(OrigArgs, [](const char *Arg) {
+    return llvm::sys::path::filename(Arg) == "CMakeFortranCompilerId.F";
+  });
+}
+
+/// Forward the invocation directly to Flang. Only reached for invocations
+/// that are not CMake's compiler-id probe, i.e. after the caller's build has
+/// already established (through a real dependency, not this mock) that
+/// flang is up to date.
+static int main_flang(
+    llvm::StringRef SelfExe, llvm::ArrayRef<const char *> OrigArgs) {
+  llvm::SmallString<256> FlangExe{llvm::sys::path::parent_path(SelfExe)};
+  llvm::sys::path::append(FlangExe, "flang");
+
+  llvm::SmallVector<llvm::StringRef> Args;
+  Args.push_back(FlangExe);
+  llvm::append_range(Args, OrigArgs);
+
+  std::string ErrMsg;
+  int RC = llvm::sys::ExecuteAndWait(FlangExe, Args, /*Env=*/std::nullopt,
+      /*Redirects=*/{}, /*SecondsToWait=*/0, /*MemoryLimit=*/0, &ErrMsg);
+  if (RC < 0)
+    fail(ErrMsg);
+  return RC;
+}
+
+/// Use Clang to emulate Flang's preprocessor, answering CMake's
+/// compiler-id probe without needing the real flang to be ready yet.
+static int main_probe(
+    llvm::StringRef SelfExe, llvm::ArrayRef<const char *> OrigArgs) {
+  llvm::WithColor::warning(llvm::errs(), "fakeflang")
+      << "Emulating flang for compiler probing\nFor anything other than "
+         "bootstrapping the Flang toolchain, just use 'flang' instead\n";
 
   llvm::SmallString<256> ClangExe{llvm::sys::path::parent_path(SelfExe)};
   llvm::sys::path::append(ClangExe, "clang");
 
-  llvm::ArrayRef<const char *> AllArgs(argv, static_cast<size_t>(argc));
-  bool hasDashO = AllArgs.size() > 1 &&
-      llvm::any_of(AllArgs.drop_front(), [](const char *Arg) {
-        return llvm::StringRef(Arg).starts_with("-o");
-      });
+  bool hasDashO = llvm::any_of(OrigArgs,
+      [](const char *Arg) { return llvm::StringRef(Arg).starts_with("-o"); });
 
   // Assemble invocation of the preprocessor
   // `-E`: Invoke the preprocessor
   // `-P`: No #line directives
   // `-D..`: Preprocessor definitions that CMake probes
+  // `-D__fakeflang_probe__=1`: To identify fakeflang probe mode
+  //                            (for regression tests)
   // `-fgnuc-version=0`: Prevent clang from implicitly defining __GNUC__;
   //                     CMake's CMakeFortranCompilerId.F.in checks
   //                     __GNUC__before __flang__, so without this flag the
@@ -89,10 +125,8 @@ int main(int argc, const char **argv) {
       "-D__flang_major__=" FLANG_VERSION_MAJOR_STRING,
       "-D__flang_minor__=" FLANG_VERSION_MINOR_STRING,
       "-D__flang_patchlevel__=" FLANG_VERSION_PATCHLEVEL_STRING,
-      "-fgnuc-version=0", "-x", "c"});
-  for (int I = 1; I < argc; ++I) {
-    llvm::StringRef arg = argv[I];
-
+      "-D__fakeflang_probe__=1", "-fgnuc-version=0", "-x", "c"});
+  for (llvm::StringRef arg : OrigArgs) {
     // Sometime CMake tries to invoke the preprocessor
     if (arg == "-cpp" || arg == "-E")
       continue;
@@ -109,3 +143,15 @@ int main(int argc, const char **argv) {
     fail(ErrMsg);
   return RC;
 }
+
+int main(int argc, const char **argv) {
+  llvm::InitLLVM X(argc, argv);
+  std::string SelfExe = getExecutablePath(argv[0]);
+  llvm::ArrayRef<const char *> OrigArgs(argv, static_cast<size_t>(argc));
+  OrigArgs = OrigArgs.drop_front();
+
+  if (isCompilerIdProbe(OrigArgs))
+    return main_probe(SelfExe, OrigArgs);
+
+  return main_flang(SelfExe, OrigArgs);
+}
diff --git a/flang/tools/flang-driver/CMakeLists.txt b/flang/tools/flang-driver/CMakeLists.txt
index d11e7f58a32b8..4dfc0d40cd55d 100644
--- a/flang/tools/flang-driver/CMakeLists.txt
+++ b/flang/tools/flang-driver/CMakeLists.txt
@@ -49,21 +49,3 @@ install(TARGETS flang DESTINATION "${CMAKE_INSTALL_BINDIR}")
 # Keep "flang-new" as a symlink for backwards compatiblity. Remove once "flang"
 # is a widely adopted name.
 add_flang_symlink(flang-new flang)
-
-
-if (NOT FLANG_STANDALONE_BUILD)
-  # This ensures that flang is always built after fakeflang. If fakeflang is
-  # built first, the bin/flang may have a newer timestamp than all of flang's
-  # source files and the make program might consider it 'up-to-date'.
-  # fakedepend.cpp which depends on fakeflang will have to be created after
-  # fakeflang and therefore have a newer timestamp than bin/flang, triggering a
-  # rebuild and therefore overwrite bin/flang with the real version.
-  add_custom_command(
-    OUTPUT fakedepend.cpp
-    DEPENDS flang-lazy
-    COMMAND "${CMAKE_COMMAND}" -E touch fakedepend.cpp
-  )
-  target_sources(flang PRIVATE
-    fakedepend.cpp
-  )
-endif ()
diff --git a/llvm/cmake/modules/LLVMExternalProjectUtils.cmake b/llvm/cmake/modules/LLVMExternalProjectUtils.cmake
index ee270d70a778d..40de0f067736a 100644
--- a/llvm/cmake/modules/LLVMExternalProjectUtils.cmake
+++ b/llvm/cmake/modules/LLVMExternalProjectUtils.cmake
@@ -229,6 +229,7 @@ function(llvm_ExternalProject_Add name source_dir)
     endif()
   endforeach()
 
+  set(maybe_configure_environment_modification "")
   if(ARG_USE_TOOLCHAIN AND NOT CMAKE_CROSSCOMPILING)
     if(CLANG_IN_TOOLCHAIN)
       if(is_msvc_target)
@@ -242,7 +243,11 @@ function(llvm_ExternalProject_Add name source_dir)
       endif()
     endif()
     if(FLANG_IN_TOOLCHAIN)
-      list(APPEND compiler_args -DCMAKE_Fortran_COMPILER=${LLVM_RUNTIME_OUTPUT_INTDIR}/flang${CMAKE_EXECUTABLE_SUFFIX})
+      # Use fakeflang to not require having all of flang built for the configure step.
+      list(APPEND compiler_args -DCMAKE_Fortran_COMPILER=${LLVM_RUNTIME_OUTPUT_INTDIR}/fakeflang${CMAKE_EXECUTABLE_SUFFIX})
+      if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.2)
+        set(maybe_configure_environment_modification "FLANG_BOOTSTRAP_PROBE=set:1")
+      endif()
     endif()
     if(lld IN_LIST TOOLCHAIN_TOOLS)
       if(is_msvc_target)
@@ -424,6 +429,7 @@ function(llvm_ExternalProject_Add name source_dir)
                ${PASSTHROUGH_VARIABLES}
     CMAKE_CACHE_DEFAULT_ARGS ${CMAKE_CACHE_DEFAULT_ARGS}
     ${build_command_arg}
+    ${maybe_configure_environment_modification}
     INSTALL_COMMAND ""
     STEP_TARGETS configure build
     BUILD_ALWAYS 1



More information about the flang-commits mailing list