[flang-commits] [flang] 5a98e96 - [Flang][CMake] Use fakeflang as dispatcher (#209482)
via flang-commits
flang-commits at lists.llvm.org
Thu Jul 16 04:19:12 PDT 2026
Author: Michael Kruse
Date: 2026-07-16T13:19:05+02:00
New Revision: 5a98e96135e223890cbf0640584e30c0db695ecf
URL: https://github.com/llvm/llvm-project/commit/5a98e96135e223890cbf0640584e30c0db695ecf
DIFF: https://github.com/llvm/llvm-project/commit/5a98e96135e223890cbf0640584e30c0db695ecf.diff
LOG: [Flang][CMake] Use fakeflang as dispatcher (#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. Only in an incremental build, would the real bin/flang already
been built created before, pointing to the currently re-built flang.
I did not think this would be possible because CMake only needs to probe
flang once and thereafter (in the incremental build) remembers the probe
result in `CMakeFiles/<version>/CMakeFortranCompiler.cmake`. CMake does
not support changing the compiler once configured 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 to do so 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.
Tested with CMake 3.20, 3.28, 3.31, 4.1, and 4.2.
Fixes #205360
Added:
Modified:
flang/test/Driver/fakeflang.F
flang/tools/CMakeLists.txt
flang/tools/fakeflang/CMakeLists.txt
flang/tools/fakeflang/fakeflang.cpp
flang/tools/flang-driver/CMakeLists.txt
llvm/cmake/modules/LLVMExternalProjectUtils.cmake
Removed:
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
diff erences.
- 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..60a880ace5073 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 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