[flang-commits] [flang] [llvm] [Flang] Add mock flang driver (PR #203481)
Michael Kruse via flang-commits
flang-commits at lists.llvm.org
Fri Jun 12 03:55:39 PDT 2026
https://github.com/Meinersbur updated https://github.com/llvm/llvm-project/pull/203481
>From 11c38beefc83d1dd1be5e1fe42d260a1141de3b1 Mon Sep 17 00:00:00 2001
From: Michael Kruse <llvm-project at meinersbur.de>
Date: Wed, 10 Jun 2026 19:15:49 +0200
Subject: [PATCH] Add mock flang driver
check-flang-rt and check-openmp depend on flang
remark only when invoked as flang
---
flang/test/Driver/fakeflang.F | 28 ++++++
flang/tools/CMakeLists.txt | 8 ++
flang/tools/fakeflang/CMakeLists.txt | 27 +++++
.../tools/fakeflang/ensure_flang_exists.cmake | 11 +++
flang/tools/fakeflang/fakeflang.cpp | 99 +++++++++++++++++++
flang/tools/flang-driver/CMakeLists.txt | 18 ++++
.../modules/LLVMExternalProjectUtils.cmake | 5 +-
llvm/runtimes/CMakeLists.txt | 27 ++---
runtimes/cmake/config-Fortran.cmake | 39 ++------
9 files changed, 216 insertions(+), 46 deletions(-)
create mode 100644 flang/test/Driver/fakeflang.F
create mode 100644 flang/tools/fakeflang/CMakeLists.txt
create mode 100644 flang/tools/fakeflang/ensure_flang_exists.cmake
create mode 100644 flang/tools/fakeflang/fakeflang.cpp
diff --git a/flang/test/Driver/fakeflang.F b/flang/test/Driver/fakeflang.F
new file mode 100644
index 0000000000000..bd4ed88409bd7
--- /dev/null
+++ b/flang/test/Driver/fakeflang.F
@@ -0,0 +1,28 @@
+! This is how CMake probes the compiler for CMAKE_Fortran_COMPILER_ID etc.
+
+! RUN: rm -rf %t
+! RUN: mkdir -p %t
+! RUN: cd %t
+
+! RUN: fakeflang -v -c --target=x86_64-unknown-linux-gnu %s
+! RUN: FileCheck %s --input-file=a.out --check-prefixes=CHECK,GNU
+! RUN: rm a.out
+
+! RUN: fakeflang -v -c --target=x86_64-pc-windows-msvc %s
+! RUN: FileCheck %s --input-file=a.out --check-prefixes=CHECK,MSVC --match-full-lines
+! RUN: rm a.out
+
+
+! CHECK: CMAKE_Fortran_COMPILER_ID=1
+CMAKE_Fortran_COMPILER_ID=__flang__
+
+! CHECK: CMAKE_Fortran_COMPILER_VERSION={{[0-9]+}} . {{[0-9]+}} . {{[0-9]+}}
+CMAKE_Fortran_COMPILER_VERSION=__flang_major__.__flang_minor__.__flang_patchlevel__
+
+! GNU: CMAKE_Fortran_SIMULATE_ID=_MSC_VER
+! MSVC: CMAKE_Fortran_SIMULATE_ID={{[0-9]+}}
+CMAKE_Fortran_SIMULATE_ID=_MSC_VER
+
+! GNU: CMAKE_Fortran_PLATFORM_ID=_WIN32
+! MSVC: CMAKE_Fortran_PLATFORM_ID=1
+CMAKE_Fortran_PLATFORM_ID=_WIN32
diff --git a/flang/tools/CMakeLists.txt b/flang/tools/CMakeLists.txt
index 1b297af74cae7..235a660221820 100644
--- a/flang/tools/CMakeLists.txt
+++ b/flang/tools/CMakeLists.txt
@@ -8,6 +8,14 @@
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 ()
+ add_subdirectory(fakeflang)
+endif ()
add_subdirectory(tco)
add_subdirectory(f18-parse-demo)
add_subdirectory(fir-opt)
diff --git a/flang/tools/fakeflang/CMakeLists.txt b/flang/tools/fakeflang/CMakeLists.txt
new file mode 100644
index 0000000000000..3503735729494
--- /dev/null
+++ b/flang/tools/fakeflang/CMakeLists.txt
@@ -0,0 +1,27 @@
+set(LLVM_LINK_COMPONENTS
+ Support
+)
+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.
+add_custom_target(flang-lazy)
+add_dependencies(flang-lazy fakeflang)
diff --git a/flang/tools/fakeflang/ensure_flang_exists.cmake b/flang/tools/fakeflang/ensure_flang_exists.cmake
new file mode 100644
index 0000000000000..4279649cd2885
--- /dev/null
+++ b/flang/tools/fakeflang/ensure_flang_exists.cmake
@@ -0,0 +1,11 @@
+# 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.
+ file(COPY_FILE "${INPUT_FILE}" "${OUTPUT_FILE}")
+endif ()
diff --git a/flang/tools/fakeflang/fakeflang.cpp b/flang/tools/fakeflang/fakeflang.cpp
new file mode 100644
index 0000000000000..500174b612862
--- /dev/null
+++ b/flang/tools/fakeflang/fakeflang.cpp
@@ -0,0 +1,99 @@
+//===-- fakeflang.cpp - Mock Flang Driver ---------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+/// Mock driver to pass CMake's compiler introspection for
+/// CMake_Fortran_COMPILER. It's purpose is to not having to build the full
+/// flang compiler for the runtimes-configure phase in bootstrapping-runtimes
+/// builds, but only when the Fortran compiler is actually needed (e.g.
+/// flang-rt-mod, libomp-mod).
+///
+/// To detect LLVMFlang, CMake executes
+///
+/// ${CMAKE_Fortran_COMPILER} -v -c -target=... CMakeFortranCompilerId.F
+///
+/// and expects a new file to appear in the working directory. This would
+/// usually be an object file (e.g. ELF), but it doesn't matter for CMake as it
+/// parses it for the preprocessor result of CMakeFortranCompilerId.F which
+/// would appear as string literals in the binary file (CMake cannot execute the
+/// file because it might be cross-compiling). Just passing it through the
+/// preprocessor yields the same result.
+///
+/// The most relevant preprocessor definition is __flang__ which leads to
+/// CMAKE_Fortran_COMPILER_ID="LLVMFlang".
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Version.inc"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/Path.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);
+}
+
+[[noreturn]] static void fail(llvm::Twine Error) {
+ llvm::WithColor::error(llvm::errs(), "fakeflang") << Error << "\n";
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, const char **argv) {
+ llvm::InitLLVM X(argc, argv);
+ std::string SelfExe = getExecutablePath(argv[0]);
+
+ 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";
+ }
+
+ 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");
+ });
+
+ // Assemble invocation of the preprocessor
+ // `-E`: Invoke the preprocessor
+ // `-P`: No #line directives
+ // `-D..`: Preprocessor definitions that CMake probes
+ // `-x c`: Usually Clang would forward Fortran files to gfortran; Interpret as
+ // C for clang to preprocess the files itself
+ // `-o`: -E by default emits to stdout, but CMake expects a new file to appear
+ // in the cwd
+ llvm::SmallVector<llvm::StringRef, 32> Args;
+ Args.append({ClangExe, "-E", "-P", "-D__flang__=1",
+ "-D__flang_major__=" FLANG_VERSION_MAJOR_STRING,
+ "-D__flang_minor__=" FLANG_VERSION_MINOR_STRING,
+ "-D__flang_patchlevel__=" FLANG_VERSION_PATCHLEVEL_STRING, "-x", "c"});
+ for (int I = 1; I < argc; ++I)
+ Args.push_back(argv[I]);
+ if (!hasDashO)
+ Args.append({"-o", "a.out"});
+
+ std::string ErrMsg;
+ int RC = llvm::sys::ExecuteAndWait(ClangExe, Args, /*Env=*/std::nullopt,
+ /*Redirects=*/{}, /*SecondsToWait=*/0, /*MemoryLimit=*/0, &ErrMsg);
+ if (RC < 0)
+ fail(ErrMsg);
+ return RC;
+}
diff --git a/flang/tools/flang-driver/CMakeLists.txt b/flang/tools/flang-driver/CMakeLists.txt
index 4dfc0d40cd55d..d11e7f58a32b8 100644
--- a/flang/tools/flang-driver/CMakeLists.txt
+++ b/flang/tools/flang-driver/CMakeLists.txt
@@ -49,3 +49,21 @@ 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 47b12dc9a3d8c..ee270d70a778d 100644
--- a/llvm/cmake/modules/LLVMExternalProjectUtils.cmake
+++ b/llvm/cmake/modules/LLVMExternalProjectUtils.cmake
@@ -100,6 +100,9 @@ function(llvm_ExternalProject_Add name source_dir)
if(NOT ARG_TOOLCHAIN_TOOLS)
set(ARG_TOOLCHAIN_TOOLS clang)
+ if (ARG_ENABLE_FORTRAN)
+ list(APPEND ARG_TOOLCHAIN_TOOLS flang-lazy)
+ endif ()
# AIX 64-bit XCOFF and big AR format is not yet supported in some of these tools.
if(NOT _cmake_system_name STREQUAL "AIX")
list(APPEND ARG_TOOLCHAIN_TOOLS lld llvm-ar llvm-ranlib llvm-nm llvm-objdump)
@@ -150,7 +153,7 @@ function(llvm_ExternalProject_Add name source_dir)
set(CLANG_IN_TOOLCHAIN On)
endif()
- if(ARG_ENABLE_FORTRAN AND TARGET flang)
+ if(flang-lazy IN_LIST TOOLCHAIN_TOOLS)
set(FLANG_IN_TOOLCHAIN On)
endif()
diff --git a/llvm/runtimes/CMakeLists.txt b/llvm/runtimes/CMakeLists.txt
index bbc91a709c553..6d81b26d2d416 100644
--- a/llvm/runtimes/CMakeLists.txt
+++ b/llvm/runtimes/CMakeLists.txt
@@ -120,18 +120,20 @@ endmacro()
# flang dependency to the main build target and any fortran module file builds,
# like 'install-libomp-mod' or `install-flang-rt-mod'.
function(add_flang_mod_deps build_target)
- if(TARGET flang)
- if(TARGET ${build_target})
- add_dependencies(${build_target} flang)
- endif()
- foreach(tgt IN LISTS ARGN)
- if(tgt MATCHES "-mod($|-)")
- if(TARGET ${tgt})
- add_dependencies(${tgt} flang)
- endif()
- endif()
- endforeach()
+ if(NOT TARGET flang)
+ return ()
+ endif ()
+
+ if(TARGET ${build_target})
+ add_dependencies(${build_target} flang)
endif()
+ foreach(tgt IN LISTS ARGN)
+ if(tgt MATCHES "-mod($|-)" OR tgt MATCHES "check-(flang-rt|openmp)($|-)")
+ if(TARGET ${tgt})
+ add_dependencies(${tgt} flang)
+ endif()
+ endif()
+ endforeach()
endfunction()
function(builtin_default_target compiler_rt_path)
@@ -364,7 +366,7 @@ function(runtime_default_target)
FOLDER "Runtimes"
${EXTRA_ARGS} ${ARG_EXTRA_ARGS})
- add_flang_mod_deps(runtimes-build ${extra_targets})
+ add_flang_mod_deps(runtimes-build ${extra_targets} ${test_targets})
endfunction()
# runtime_register_target(name)
@@ -544,6 +546,7 @@ if(build_runtimes)
if(LLVM_INCLUDE_TESTS)
foreach(dep FileCheck
clang
+ flang-lazy
count
lld
lli
diff --git a/runtimes/cmake/config-Fortran.cmake b/runtimes/cmake/config-Fortran.cmake
index e9ac8e7b7eac3..74fd15021f0a0 100644
--- a/runtimes/cmake/config-Fortran.cmake
+++ b/runtimes/cmake/config-Fortran.cmake
@@ -88,27 +88,15 @@ if (CMAKE_Fortran_COMPILER)
# cannot use CMAKE_Fortran_COMPILER_ID.
cmake_path(GET CMAKE_Fortran_COMPILER STEM _Fortran_COMPILER_STEM)
if (_Fortran_COMPILER_STEM STREQUAL "flang-new" OR _Fortran_COMPILER_STEM STREQUAL "flang")
- # Force the compiler ID so CMake does not try to run the compiler for
- # identification. In a bootstrapping build the Flang binary may not be
- # built yet at configure time (only CMAKE_Fortran_COMPILER_WORKS is set).
- # FIXME: flang has no equivalent to clang-cl, so
- # CMAKE_Fortran_SIMULATE_ID=GNU should be the only correct value. CMake may
- # imply that supports different toolchains for each language but in
- # practice is doesn't. In particular, the last enabled
- # language will overwrite global variables such as CMAKE_LINK_LIBRARY_FLAG
- # depending on CMAKE_<lang>_SIMULATE_ID, i.e. they cannot be different.
- set(CMAKE_Fortran_COMPILER_ID "LLVMFlang")
- set(CMAKE_Fortran_COMPILER_ID_RUN TRUE)
- set(CMAKE_Fortran_COMPILER_FORCED TRUE)
- set(CMAKE_Fortran_COMPILER_VERSION "${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}")
- set(CMAKE_Fortran_SIMULATE_ID "${CMAKE_CXX_SIMULATE_ID}")
- set(CMAKE_Fortran_SIMULATE_VERSION "${CMAKE_CXX_SIMULATE_VERSION}")
- set(CMAKE_Fortran_COMPILER_SUPPORTS_F90 1)
- set(CMAKE_Fortran_PLATFORM_ID "${CMAKE_CXX_PLATFORM_ID}")
-
# CMake 3.24 is the first version of CMake that directly recognizes Flang.
# LLVM's requirement is only CMake 3.20, teach CMake 3.20-3.23 how to use Flang, if used.
if (CMAKE_VERSION VERSION_LESS "3.24")
+ include(CMakeForceCompiler)
+ CMAKE_FORCE_Fortran_COMPILER("${CMAKE_Fortran_COMPILER}" "LLVMFlang")
+
+ set(CMAKE_Fortran_COMPILER_ID "LLVMFlang")
+ set(CMAKE_Fortran_COMPILER_VERSION "${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}")
+
set(CMAKE_Fortran_SUBMODULE_SEP "-")
set(CMAKE_Fortran_SUBMODULE_EXT ".mod")
@@ -159,21 +147,6 @@ else ()
return ()
endif ()
-# In a bootstrapping build the Fortran compiler may not have been built yet.
-# Create a placeholder so CMake's enable_language() existence check passes.
-# The build-order dependency in add_flang_mod_deps ensures the real binary is
-# built before anything tries to invoke this placeholder.
-if (CMAKE_Fortran_COMPILER_FORCED AND NOT EXISTS "${CMAKE_Fortran_COMPILER}")
- get_filename_component(_compiler_dir "${CMAKE_Fortran_COMPILER}" DIRECTORY)
- file(MAKE_DIRECTORY "${_compiler_dir}")
- file(WRITE "${CMAKE_Fortran_COMPILER}" "stub")
- # Ninja uses file mtimes to decide whether build outputs are up-to-date.
- # If this placeholder's mtime is recent it may match what is recorded in
- # .ninja_log, causing ninja to skip building the real compiler binary.
- # Set it so that any subsequent real build always has a newer mtime.
- execute_process(COMMAND touch -t 197001020000 "${CMAKE_Fortran_COMPILER}"
- ERROR_QUIET)
-endif ()
include(CheckLanguage)
check_language(Fortran)
More information about the flang-commits
mailing list