diff --git a/clang/include/clang/Driver/Distro.h b/clang/include/clang/Driver/Distro.h --- a/clang/include/clang/Driver/Distro.h +++ b/clang/include/clang/Driver/Distro.h @@ -23,6 +23,8 @@ class Distro { public: enum DistroType { + // Special value means that no detection was performed yet. + UninitializedDistro = -1, // NB: Releases of a particular Linux distro should be kept together // in this enum, because some tests are done by integer comparison against // the first and last known member in the family, e.g. IsRedHat(). diff --git a/clang/lib/Driver/Distro.cpp b/clang/lib/Driver/Distro.cpp --- a/clang/lib/Driver/Distro.cpp +++ b/clang/lib/Driver/Distro.cpp @@ -19,30 +19,42 @@ using namespace clang::driver; using namespace clang; -static Distro::DistroType DetectDistro(llvm::vfs::FileSystem &VFS, - const llvm::Triple &TargetOrHost) { - // If we don't target Linux, no need to check the distro. This saves a few - // OS calls. - if (!TargetOrHost.isOSLinux()) - return Distro::UnknownDistro; +static Distro::DistroType DetectOsRelease(llvm::vfs::FileSystem &VFS) { + Distro::DistroType Version = Distro::UnknownDistro; + llvm::ErrorOr> File = + VFS.getBufferForFile("/etc/os-release"); + if (!File) + File = VFS.getBufferForFile("/usr/lib/os-release"); - // If the host is not running Linux, and we're backed by a real file system, - // no need to check the distro. This is the case where someone is - // cross-compiling from BSD or Windows to Linux, and it would be meaningless - // to try to figure out the "distro" of the non-Linux host. - IntrusiveRefCntPtr RealFS = - llvm::vfs::getRealFileSystem(); - llvm::Triple HostTriple(llvm::sys::getProcessTriple()); - if (!HostTriple.isOSLinux() && &VFS == RealFS.get()) - return Distro::UnknownDistro; + if (File) { + SmallVector Lines; + File.get()->getBuffer().split(Lines, "\n"); + // Obviously this can be improved a lot. + for (StringRef Line : Lines) + if (Version == Distro::UnknownDistro && Line.startswith("ID=")) + Version = llvm::StringSwitch(Line.substr(3)) + .Case("fedora", Distro::Fedora) + .Case("gentoo", Distro::Gentoo) + .Case("arch", Distro::ArchLinux) + // On SLES, /etc/os-release was introduced in SLES 11. + .Case("sles", Distro::OpenSUSE) + .Case("opensuse", Distro::OpenSUSE) + .Case("exherbo", Distro::Exherbo) + .Default(Distro::UnknownDistro); + } + return Version; +} + +static Distro::DistroType DetectLsbRelease(llvm::vfs::FileSystem &VFS) { + Distro::DistroType Version = Distro::UnknownDistro; llvm::ErrorOr> File = VFS.getBufferForFile("/etc/lsb-release"); + if (File) { - StringRef Data = File.get()->getBuffer(); SmallVector Lines; - Data.split(Lines, "\n"); - Distro::DistroType Version = Distro::UnknownDistro; + File.get()->getBuffer().split(Lines, "\n"); + for (StringRef Line : Lines) if (Version == Distro::UnknownDistro && Line.startswith("DISTRIB_CODENAME=")) Version = llvm::StringSwitch(Line.substr(17)) @@ -73,11 +85,28 @@ .Case("focal", Distro::UbuntuFocal) .Case("groovy", Distro::UbuntuGroovy) .Default(Distro::UnknownDistro); - if (Version != Distro::UnknownDistro) - return Version; } + return Version; +} + +static Distro::DistroType DetectDistro(llvm::vfs::FileSystem &VFS) { + Distro::DistroType Version = Distro::UnknownDistro; + + // Newer freedesktop.org's compilant systemd-based systems + // should provide /etc/os-release or /usr/lib/os-release. + Version = DetectOsRelease(VFS); + if (Version != Distro::UnknownDistro) + return Version; + + // Older systems might provide /etc/lsb-release. + Version = DetectLsbRelease(VFS); + if (Version != Distro::UnknownDistro) + return Version; + + // Otherwise try some distro-specific quirks for RedHat... + llvm::ErrorOr> File + = VFS.getBufferForFile("/etc/redhat-release"); - File = VFS.getBufferForFile("/etc/redhat-release"); if (File) { StringRef Data = File.get()->getBuffer(); if (Data.startswith("Fedora release")) @@ -95,6 +124,7 @@ return Distro::UnknownDistro; } + // ...for Debian File = VFS.getBufferForFile("/etc/debian_version"); if (File) { StringRef Data = File.get()->getBuffer(); @@ -130,6 +160,7 @@ .Default(Distro::UnknownDistro); } + // ...for SUSE File = VFS.getBufferForFile("/etc/SuSE-release"); if (File) { StringRef Data = File.get()->getBuffer(); @@ -153,6 +184,7 @@ return Distro::UnknownDistro; } + // ...and others. if (VFS.exists("/etc/exherbo-release")) return Distro::Exherbo; @@ -168,5 +200,37 @@ return Distro::UnknownDistro; } +static Distro::DistroType GetDistro(llvm::vfs::FileSystem &VFS, + const llvm::Triple &TargetOrHost) { + static Distro::DistroType Type = Distro::UninitializedDistro; + + // If we don't target Linux, no need to check the distro. This saves a few + // OS calls. + if (!TargetOrHost.isOSLinux()) + return Distro::UnknownDistro; + + // True if we're backed by a real file system. + const bool onRealFS = (llvm::vfs::getRealFileSystem() == &VFS); + + // If we're backed by a real file system and had performed detection + // already, return the result. This saves a few more OS calls assuming + // there was no underlying on-the fly distro change. + if (onRealFS && Type != Distro::UninitializedDistro) + return Type; + + // If the host is not running Linux, and we're backed by a real file + // system, no need to check the distro. This is the case where someone + // is cross-compiling from BSD or Windows to Linux, and it would be + // meaningless to try to figure out the "distro" of the non-Linux host. + llvm::Triple HostTriple(llvm::sys::getProcessTriple()); + if (!HostTriple.isOSLinux() && onRealFS) + Type = Distro::UnknownDistro; + else + // Perform the detection and save the result. + Type = DetectDistro(VFS); + + return Type; +} + Distro::Distro(llvm::vfs::FileSystem &VFS, const llvm::Triple &TargetOrHost) - : DistroVal(DetectDistro(VFS, TargetOrHost)) {} + : DistroVal(GetDistro(VFS, TargetOrHost)) {}