[clang] [MinGW] Avoid adding <base>/include and <base>/lib when cross compiling
authorMartin Storsjö <martin@martin.st>
Tue, 22 Nov 2022 14:12:39 +0000 (16:12 +0200)
committerMartin Storsjö <martin@martin.st>
Mon, 16 Jan 2023 22:39:12 +0000 (00:39 +0200)
The MinGW compiler driver first tries to deduce the root of
the toolchain installation (either clang itself or a separate
cross mingw gcc installation). On top of this root, a number
of include and lib paths are added (some added unconditionally,
some only if they exist):
- <base>/x86_64-w64-mingw32/include
- <base>/include
- <base>/include/x86_64-w64-windows-gnu
(Some more are also added for libstdc++ and/or libc++.)

The first one is the one commonly used for MinGW targets so
far. For LLVM runtimes installed with the
LLVM_ENABLE_PER_TARGET_RUNTIME_DIR option, the latter two are
used though (this is currently not the default, not yet at least).

For cross compiling, if base is a separate dedicated directory,
this is fine, but when using the sysroots of a distro-installed
cross mingw toolchain, base is /usr - and having /usr/include
in the include path for cross compilation is a potential
source for problems; see
https://github.com/llvm/llvm-project/issues/59871.

If not cross compiling though, <base>/include needs to be included
too. E.g. in the case of msys2, most headers are in e.g.
/mingw64/include while the compiler is /mingw64/bin/clang.

When cross compiling, if the sysroot has been explicitly set
by the user, keep <base>/include too. (In the case of a distro
provided cross gcc toolchain in /usr, the sysroot needs to be set
to /usr and not /usr/x86_64-w64-mingw32 though, to be able to find
libgcc files under /usr/lib/gcc/x86_64-w64-mingw32. So with such a
toolchain, setting the sysroot explicitly does retain the problem.)

All in all - this avoids adding /usr/include and /usr/lib to the
include/lib paths when doing mingw cross compilation with a
distro-provided sysroot in /usr/x86_64-w64-mingw32.

Test that the include directory is omitted in the mingw-sysroot.cpp
tests, when cross compiling. That test is only ever executed on
non-Windows hosts, since it uses symlinks to set up fake environments
with colocated compilers and header/lib directories. There aren't
really any current corresponding tests for the same implicit behaviours
when actually running on Windows.

Differential Revision: https://reviews.llvm.org/D141206

clang/lib/Driver/ToolChains/MinGW.cpp
clang/test/Driver/mingw-sysroot.cpp

index 4d4c9b5..908484f 100644 (file)
@@ -348,6 +348,15 @@ void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA,
                                          Exec, CmdArgs, Inputs, Output));
 }
 
+static bool isCrossCompiling(const llvm::Triple &T, bool RequireArchMatch) {
+  llvm::Triple HostTriple(llvm::Triple::normalize(LLVM_HOST_TRIPLE));
+  if (HostTriple.getOS() != llvm::Triple::Win32)
+    return true;
+  if (RequireArchMatch && HostTriple.getArch() != T.getArch())
+    return true;
+  return false;
+}
+
 // Simplified from Generic_GCC::GCCInstallationDetector::ScanLibDirForGCCTriple.
 static bool findGccVersion(StringRef LibDir, std::string &GccLibDir,
                            std::string &Ver,
@@ -487,7 +496,13 @@ toolchains::MinGW::MinGW(const Driver &D, const llvm::Triple &Triple,
   getFilePaths().push_back(
       (Base + SubdirName + llvm::sys::path::get_separator() + "mingw/lib").str());
 
-  getFilePaths().push_back(Base + "lib");
+  // Only include <base>/lib if we're not cross compiling (not even for
+  // windows->windows to a different arch), or if the sysroot has been set
+  // (where we presume the user has pointed it at an arch specific
+  // subdirectory).
+  if (!::isCrossCompiling(getTriple(), /*RequireArchMatch=*/true) ||
+      getDriver().SysRoot.size())
+    getFilePaths().push_back(Base + "lib");
 
   NativeLLVMSupport =
       Args.getLastArgValue(options::OPT_fuse_ld_EQ, CLANG_DEFAULT_LINKER)
@@ -649,7 +664,13 @@ void toolchains::MinGW::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
   addSystemInclude(DriverArgs, CC1Args,
                    Base + SubdirName + llvm::sys::path::get_separator() + "usr/include");
 
-  addSystemInclude(DriverArgs, CC1Args, Base + "include");
+  // Only include <base>/include if we're not cross compiling (but do allow it
+  // if we're on Windows and building for Windows on another architecture),
+  // or if the sysroot has been set (where we presume the user has pointed it
+  // at an arch specific subdirectory).
+  if (!::isCrossCompiling(getTriple(), /*RequireArchMatch=*/false) ||
+      getDriver().SysRoot.size())
+    addSystemInclude(DriverArgs, CC1Args, Base + "include");
 }
 
 void toolchains::MinGW::addClangTargetOptions(
index 17f2b97..911dab4 100644 (file)
 // If we find a gcc in the path with the right triplet prefix, pick that as
 // sysroot:
 
-// RUN: env "PATH=%T/testroot-gcc/bin:%PATH%" %clang -target x86_64-w64-mingw32 -rtlib=platform -stdlib=libstdc++ --sysroot="" -c -### %s 2>&1 | FileCheck -check-prefix=CHECK_TESTROOT_GCC %s
+// This test is only executed on non-Windows systems, i.e. only when
+// cross compiling. Check that we don't add the tool root's plain include
+// directory to the path - this would end up including /usr/include for
+// cross toolchains installed in /usr.
+
+// RUN: env "PATH=%T/testroot-gcc/bin:%PATH%" %clang -target x86_64-w64-mingw32 -rtlib=platform -stdlib=libstdc++ --sysroot="" -c -### %s 2>&1 | FileCheck -check-prefix=CHECK_TESTROOT_GCC %s --implicit-check-not="\"{{.*}}/testroot-gcc{{/|\\\\}}include\""
 // CHECK_TESTROOT_GCC: "-internal-isystem" "[[BASE:[^"]+]]/testroot-gcc{{/|\\\\}}lib{{/|\\\\}}gcc{{/|\\\\}}x86_64-w64-mingw32{{/|\\\\}}10.2-posix{{/|\\\\}}include{{/|\\\\}}c++"
 // CHECK_TESTROOT_GCC-SAME: {{^}} "-internal-isystem" "[[BASE]]/testroot-gcc{{/|\\\\}}lib{{/|\\\\}}gcc{{/|\\\\}}x86_64-w64-mingw32{{/|\\\\}}10.2-posix{{/|\\\\}}include{{/|\\\\}}c++{{/|\\\\}}x86_64-w64-mingw32"
 // CHECK_TESTROOT_GCC-SAME: {{^}} "-internal-isystem" "[[BASE]]/testroot-gcc{{/|\\\\}}lib{{/|\\\\}}gcc{{/|\\\\}}x86_64-w64-mingw32{{/|\\\\}}10.2-posix{{/|\\\\}}include{{/|\\\\}}c++{{/|\\\\}}backward"
 // CHECK_TESTROOT_GCC: "-internal-isystem" "[[BASE]]/testroot-gcc{{/|\\\\}}x86_64-w64-mingw32{{/|\\\\}}include"
 
 
+// If we pass --sysroot explicitly, then we do include <sysroot>/include
+// even when cross compiling.
+// RUN: %clang -target x86_64-w64-mingw32 -rtlib=platform -stdlib=libstdc++ --sysroot="%T/testroot-gcc" -c -### %s 2>&1 | FileCheck -check-prefix=CHECK_TESTROOT_GCC_EXPLICIT %s
+
+// CHECK_TESTROOT_GCC_EXPLICIT: "-internal-isystem" "{{[^"]+}}/testroot-gcc{{/|\\\\}}include"
+
+
 // If there's a matching sysroot next to the clang binary itself, prefer that
 // over a gcc in the path: