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 $ 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=$" "-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 #include -#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 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 OrigArgs) { + llvm::SmallString<256> FlangExe{llvm::sys::path::parent_path(SelfExe)}; + llvm::sys::path::append(FlangExe, "flang"); + + llvm::SmallVector 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 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 AllArgs(argv, static_cast(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 OrigArgs(argv, static_cast(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