diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -3850,6 +3850,21 @@ Operating System Features and Limitations ----------------------------------------- +Apple +^^^^^ + +On Apple platforms, the standard headers and libraries are not provided by +the base system and are instead part of the Xcode SDK application. The location +of the SDK is determined in the following priority: + +- If passed to Clang, the ``-isysroot`` option specifies the path to the SDK. + +- If the sysroot isn't provided, the ``SDKROOT`` environment variable is checked. + This variable is set by various Xcode tools. + +- If the ``--infer-sdkroot-from-xcrun`` option is provided, Clang uses Xcode's + ``xcrun`` tool to find the SDK. + Windows ^^^^^^^ diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3375,6 +3375,8 @@ MarshallingInfoFlag>; def verify_pch : Flag<["-"], "verify-pch">, Group, Flags<[CC1Option]>, HelpText<"Load and verify that a pre-compiled header file is not stale">; +def infer_sdkroot_from_xcrun : Flag<["--"], "infer-sdkroot-from-xcrun">, + Flags<[NoXarchOption]>, HelpText<"Detect Xcode SDK path with xcrun">; def init : Separate<["-"], "init">; def install__name : Separate<["-"], "install_name">; def iprefix : JoinedOrSeparate<["-"], "iprefix">, Group, Flags<[CC1Option]>, diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp --- a/clang/lib/Driver/ToolChains/Darwin.cpp +++ b/clang/lib/Driver/ToolChains/Darwin.cpp @@ -18,15 +18,22 @@ #include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/Options.h" #include "clang/Driver/SanitizerArgs.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Option/ArgList.h" #include "llvm/ProfileData/InstrProf.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FileUtilities.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/Threading.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/TargetParser/TargetParser.h" #include // ::getenv +#include // std::unique_ptr using namespace clang::driver; using namespace clang::driver::tools; @@ -2098,21 +2105,89 @@ void Darwin::AddDeploymentTarget(DerivedArgList &Args) const { const OptTable &Opts = getDriver().getOpts(); - // Support allowing the SDKROOT environment variable used by xcrun and other - // Xcode tools to define the default sysroot, by making it the default for - // isysroot. + // On Apple platforms, standard headers and libraries are not provided with + // the base system (e.g. in /usr/{include,lib}). Instead, they are provided + // in various SDKs for the different Apple platforms. Clang needs to know + // where that SDK lives, and there are a couple ways this can be achieved: + // + // (1) If `-isysroot ` is passed explicitly, use that. if (const Arg *A = Args.getLastArg(options::OPT_isysroot)) { // Warn if the path does not exist. if (!getVFS().exists(A->getValue())) getDriver().Diag(clang::diag::warn_missing_sysroot) << A->getValue(); - } else { - if (char *env = ::getenv("SDKROOT")) { - // We only use this value as the default if it is an absolute path, - // exists, and it is not the root path. - if (llvm::sys::path::is_absolute(env) && getVFS().exists(env) && - StringRef(env) != "/") { - Args.append(Args.MakeSeparateArg( - nullptr, Opts.getOption(options::OPT_isysroot), env)); + } + + // (2) If the SDKROOT environment variable is defined and points to a valid + // path, use that. $SDKROOT is set by `xcrun` and other Xcode tools, so + // running `xcrun clang` will work by going through this path. + else if (char *env = ::getenv("SDKROOT")) { + // We only use this value as the default if it is an absolute path, + // exists, and it is not the root path. + if (llvm::sys::path::is_absolute(env) && getVFS().exists(env) && + StringRef(env) != "/") { + Args.append(Args.MakeSeparateArg( + nullptr, Opts.getOption(options::OPT_isysroot), env)); + } + } + + // (3) Otherwise, we try to guess the path of the default SDK by running + // `xcrun --show-sdk-path`. This won't always be correct, but if the + // user wants to use the non-default SDK, they should specify it + // explicitly with methods (1) or (2) above. + else if (Args.hasArg(options::OPT_infer_sdkroot_from_xcrun)) { + llvm::SmallString<64> OutputFile; + llvm::sys::fs::createTemporaryFile("print-sdk-path", "" /* No Suffix */, + OutputFile); + llvm::FileRemover OutputRemover(OutputFile.c_str()); + std::optional Redirects[] = {{""}, OutputFile.str(), {""}}; + + std::optional SDKName = std::nullopt; + switch (getTriple().getOS()) { + case llvm::Triple::OSType::WatchOS: + if (getTriple().isSimulatorEnvironment()) + SDKName = "watchsimulator"; + else + SDKName = "watchos"; + break; + case llvm::Triple::OSType::TvOS: + if (getTriple().isSimulatorEnvironment()) + SDKName = "appletvsimulator"; + else + SDKName = "appletvos"; + break; + case llvm::Triple::OSType::IOS: + if (getTriple().isSimulatorEnvironment()) + SDKName = "iphonesimulator"; + else + SDKName = "iphoneos"; + break; + case llvm::Triple::OSType::Darwin: + case llvm::Triple::OSType::MacOSX: + SDKName = "macosx"; + break; + case llvm::Triple::OSType::DriverKit: + SDKName = "driverkit"; + break; + default: + llvm_unreachable("unknown kind of Darwin platform"); + } + + if (SDKName) { + int Result = llvm::sys::ExecuteAndWait( + "/usr/bin/xcrun", + {"/usr/bin/xcrun", "--sdk", *SDKName, "--show-sdk-path"}, + /* Inherit environment from parent process */ std::nullopt, Redirects, + /* SecondsToWait */ 0, /*MemoryLimit*/ 0); + if (Result == 0) { + llvm::ErrorOr> OutputBuf = + llvm::MemoryBuffer::getFile(OutputFile.c_str(), /* IsText */ true); + if (OutputBuf) { + llvm::StringRef SdkPath = (*OutputBuf)->getBuffer().trim(); + if (getVFS().exists(SdkPath)) { + Args.append(Args.MakeSeparateArg( + nullptr, Opts.getOption(options::OPT_isysroot), SdkPath)); + } + } } } } diff --git a/clang/test/Driver/darwin-sdk-detect.c b/clang/test/Driver/darwin-sdk-detect.c new file mode 100644 --- /dev/null +++ b/clang/test/Driver/darwin-sdk-detect.c @@ -0,0 +1,20 @@ +// REQUIRES: system-darwin, ios-sdk, macos-sdk + +// Check that we default to running `xcrun --show-sdk-path` if there is no +// SDKROOT defined in the environment. +// +// RUN: env -u SDKROOT %clang --infer-sdkroot-from-xcrun -target x86_64-apple-macos -c %s -### 2> %t.log +// RUN: FileCheck --check-prefix=CHECK-XC < %t.log %s +// +// CHECK-XC: clang +// CHECK-XC: "-cc1" +// CHECK-XC: "-isysroot" "{{.*MacOSX[0-9\.]*\.sdk}}" + +// Check once again that we default to running `xcrun`, this time with another target. +// +// RUN: env -u SDKROOT %clang --infer-sdkroot-from-xcrun -target arm64-apple-ios -c %s -### 2> %t.log +// RUN: FileCheck --check-prefix=CHECK-XC-IOS < %t.log %s +// +// CHECK-XC-IOS: clang +// CHECK-XC-IOS: "-cc1" +// CHECK-XC-IOS: "-isysroot" "{{.*iPhoneOS[0-9\.]*\.sdk}}" diff --git a/clang/test/Driver/lit.local.cfg b/clang/test/Driver/lit.local.cfg --- a/clang/test/Driver/lit.local.cfg +++ b/clang/test/Driver/lit.local.cfg @@ -1,4 +1,5 @@ from lit.llvm import llvm_config +import subprocess config.suffixes = ['.c', '.cpp', '.cppm', '.h', '.m', '.mm', '.S', '.s', '.f90', '.F90', '.f95', '.cu', '.rs', '.cl', '.clcpp', '.hip', '.hipi', '.hlsl'] @@ -24,3 +25,9 @@ if config.ppc_linux_default_ieeelongdouble: config.available_features.add('ppc_linux_default_ieeelongdouble') + +if os.path.exists('/usr/bin/xcrun') and sys.platform == 'darwin': + if subprocess.run(['/usr/bin/xcrun', '--sdk', 'macosx', '--show-sdk-path']).returncode == 0: + config.available_features.add('macos-sdk') + if subprocess.run(['/usr/bin/xcrun', '--sdk', 'iphoneos', '--show-sdk-path']).returncode == 0: + config.available_features.add('ios-sdk')