blob: 8235e82e9ade4b69ef6258f79b0441d74f4dc263 [file] [edit]
//===-- 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".
///
/// 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.
//
//===----------------------------------------------------------------------===//
#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/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/WithColor.h"
#include <cstdint>
#include <string>
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);
}
/// 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;
return llvm::any_of(OrigArgs, [](const char *Arg) {
llvm::StringRef fname = llvm::sys::path::filename(Arg);
return fname == "CMakeFortranCompilerId.F" || fname == "CMakeTestGNU.c";
});
}
/// 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");
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
// compiler is misidentified as GNU.
// `-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,
"-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;
Args.push_back(arg);
}
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;
}
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);
}