diff --git a/cmake/modules/RootMacros.cmake b/cmake/modules/RootMacros.cmake index 0e781d0e5d1e1..5ee380bf32047 100644 --- a/cmake/modules/RootMacros.cmake +++ b/cmake/modules/RootMacros.cmake @@ -426,7 +426,7 @@ function(ROOT_GENERATE_DICTIONARY dictionary) set(rootmap_name ${library_output_dir}/${libprefix}${deduced_arg_module}.rootmap) endif() - if(CMAKE_ROOTTEST_NOROOTMAP) + if(CMAKE_ROOTTEST_NOROOTMAP OR cpp_module_file) set(rootmap_name ) set(rootmapargs ) else() diff --git a/core/base/src/TSystem.cxx b/core/base/src/TSystem.cxx index 2b2e22e7b52c0..5df008cdc2c44 100644 --- a/core/base/src/TSystem.cxx +++ b/core/base/src/TSystem.cxx @@ -21,14 +21,6 @@ that the method should be overridden in a derived class), which allows a simple partial implementation for new OS'es. */ -#ifdef WIN32 -#include -#endif -#include -#include -#include -#include - #include #include "Riostream.h" #include "TSystem.h" @@ -55,6 +47,14 @@ allows a simple partial implementation for new OS'es. #include "RConfigure.h" #include "THashList.h" +#include +#include +#include + +#ifdef WIN32 +#include +#endif + const char *gRootDir; const char *gProgName; const char *gProgPath; @@ -3353,6 +3353,36 @@ int TSystem::CompileMacro(const char *filename, Option_t *opt, linkDepLibraries = linkLibs & 0x1; } + // FIXME: Triggers clang false positive warning -Wunused-lambda-capture. + /*constexpr const*/ bool useCxxModules = +#ifdef R__USE_CXXMODULES + true; +#else + false; +#endif + + auto LoadLibrary = [useCxxModules, produceRootmap](const TString& lib) { + // We have no rootmap files or modules to construct `-l` flags enabling + // explicit linking. We have to resolve the dependencies by ourselves + // taking the job of the dyld. + // FIXME: This is a rare case where we have rootcling running with + // modules disabled. Remove this code once we fully switch to modules, + // or implement a special flag in rootcling which selective enables + // modules for dependent libraries and does not produce a module for + // the ACLiC library. + if (useCxxModules && !produceRootmap) { + using namespace std; + string deps = gInterpreter->GetSharedLibDeps(lib, /*tryDyld*/true); + istringstream iss(deps); + vector libs {istream_iterator{iss}, istream_iterator{}}; + // Skip the first element: it is a relative path to `lib`. + for (auto I = libs.begin() + 1, E = libs.end(); I != E; ++I) + if (gInterpreter->Load(I->c_str(), /*system*/false) < 0) + return false; // failure + } + return !gSystem->Load(lib); + }; + if (!recompile) { // The library already exist, let's just load it. if (loadLib) { @@ -3367,7 +3397,7 @@ int TSystem::CompileMacro(const char *filename, Option_t *opt, gInterpreter->LoadLibraryMap(libmapfilename); } - return !gSystem->Load(library); + return LoadLibrary(library); } else return kTRUE; } @@ -3478,9 +3508,11 @@ int TSystem::CompileMacro(const char *filename, Option_t *opt, TString mapfileout = mapfile + ".out"; Bool_t needLoadMap = kFALSE; - if (gInterpreter->GetSharedLibDeps(libname) !=0 ) { - gInterpreter->UnloadLibraryMap(libname); - needLoadMap = kTRUE; + if (!useCxxModules) { + if (gInterpreter->GetSharedLibDeps(libname) !=0 ) { + gInterpreter->UnloadLibraryMap(libname); + needLoadMap = kTRUE; + } } std::ofstream mapfileStream( mapfilein, std::ios::out ); @@ -3511,11 +3543,6 @@ int TSystem::CompileMacro(const char *filename, Option_t *opt, } mapfileStream.close(); - bool useCxxModules = false; -#ifdef R__USE_CXXMODULES - useCxxModules = true; -#endif - // ======= Generate the rootcling command line TString rcling = "rootcling"; PrependPathName(TROOT::GetBinDir(), rcling); @@ -3770,8 +3797,10 @@ int TSystem::CompileMacro(const char *filename, Option_t *opt, gInterpreter->LoadLibraryMap(libmapfilename); } if (verboseLevel>3 && withInfo) ::Info("ACLiC","loading the shared library"); - if (loadLib) result = !gSystem->Load(library); - else result = kTRUE; + if (loadLib) + result = LoadLibrary(library); + else + result = kTRUE; if ( !result ) { if (verboseLevel>3 && withInfo) { diff --git a/core/dictgen/src/TModuleGenerator.cxx b/core/dictgen/src/TModuleGenerator.cxx index e684e1a620182..329dd83f302da 100644 --- a/core/dictgen/src/TModuleGenerator.cxx +++ b/core/dictgen/src/TModuleGenerator.cxx @@ -395,8 +395,9 @@ void TModuleGenerator::WriteRegistrationSource(std::ostream &out, const std::str { if (hasCxxModule) { std::string emptyStr = "\"\""; - WriteRegistrationSourceImpl(out, GetDictionaryName(), GetDemangledDictionaryName(), {}, {}, emptyStr, "{}", - emptyStr, "0", "0", + WriteRegistrationSourceImpl(out, GetDictionaryName(), GetDemangledDictionaryName(), {}, {}, + fwdDeclString, "{}", + emptyStr, headersClassesMapString, "0", /*HasCxxModule*/ true); return; } diff --git a/core/dictgen/src/rootcling_impl.cxx b/core/dictgen/src/rootcling_impl.cxx index b76d26050eb11..8a2c53212054b 100644 --- a/core/dictgen/src/rootcling_impl.cxx +++ b/core/dictgen/src/rootcling_impl.cxx @@ -3370,8 +3370,8 @@ void ExtractHeadersForDecls(const RScanner::ClassColl_t &annotatedRcds, //////////////////////////////////////////////////////////////////////////////// /// Generate the fwd declarations of the selected entities -std::string GenerateFwdDeclString(const RScanner &scan, - const cling::Interpreter &interp) +static std::string GenerateFwdDeclString(const RScanner &scan, + const cling::Interpreter &interp) { std::string newFwdDeclString; @@ -4839,18 +4839,17 @@ int RootClingMain(int argc, } - const std::string headersClassesMapString = GenerateStringFromHeadersForClasses(headersDeclsMap, - detectedUmbrella, - true); + std::string headersClassesMapString = "\"\""; std::string fwdDeclsString = "\"\""; - if (!gDriverConfig->fBuildingROOTStage1) { - if (writeEmptyRootPCM) { - fwdDeclsString = "nullptr"; - } else { - fwdDeclsString = GenerateFwdDeclString(scan, interp); + if (!cxxmodule) { + headersClassesMapString = GenerateStringFromHeadersForClasses(headersDeclsMap, + detectedUmbrella, + true); + if (!gDriverConfig->fBuildingROOTStage1) { + if (!writeEmptyRootPCM) + fwdDeclsString = GenerateFwdDeclString(scan, interp); } } - modGen.WriteRegistrationSource(dictStream, fwdDeclnArgsToKeepString, headersClassesMapString, fwdDeclsString, extraIncludes, cxxmodule); // If we just want to inline the input header, we don't need diff --git a/core/meta/inc/TInterpreter.h b/core/meta/inc/TInterpreter.h index c5dad5e53b79e..10c29cd583f89 100644 --- a/core/meta/inc/TInterpreter.h +++ b/core/meta/inc/TInterpreter.h @@ -157,7 +157,7 @@ class TInterpreter : public TNamed { virtual char *GetPrompt() = 0; virtual const char *GetSharedLibs() = 0; virtual const char *GetClassSharedLibs(const char *cls) = 0; - virtual const char *GetSharedLibDeps(const char *lib) = 0; + virtual const char *GetSharedLibDeps(const char *lib, bool tryDyld = false) = 0; virtual const char *GetIncludePath() = 0; virtual const char *GetSTLIncludePath() const { return ""; } virtual TObjArray *GetRootMapFiles() const = 0; diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index 99d7817b0ffa1..7027a65116b44 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -116,9 +116,10 @@ clang/LLVM technology. #include "llvm/Support/raw_ostream.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" +#include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/ObjectFile.h" -#include "llvm/Support/FileSystem.h" #include "llvm/Object/SymbolicFile.h" +#include "llvm/Support/FileSystem.h" #include #include @@ -6112,16 +6113,56 @@ static bool LookupBloomFilter(llvm::object::ObjectFile *soFile, uint32_t hash) { return (bitmask & word) == bitmask; } -// Lookup for normal symbols -static bool LookupNormalSymbols(llvm::object::ObjectFile *RealSoFile, const std::string& mangled_name, const std::string& LibName) +/// Looks up symbols from a an object file, representing the library. +///\returns true on success. +static bool FindSymbol(const std::string &library_filename, + const std::string &mangled_name) { + auto ObjF = llvm::object::ObjectFile::createObjectFile(library_filename); + if (!ObjF) { + Warning("TCling__FindSymbol", "Failed to read object file %s", library_filename.c_str()); + return false; + } + + llvm::object::ObjectFile *BinObjFile = ObjF.get().getBinary(); + uint32_t hashedMangle = GNUHash(mangled_name); // Check Bloom filter. If false, it means that this library doesn't contain mangled_name defenition - if (!LookupBloomFilter(RealSoFile, hashedMangle)) + if (!LookupBloomFilter(BinObjFile, hashedMangle)) + return false; + + for (const auto &S : BinObjFile->symbols()) { + uint32_t Flags = S.getFlags(); + // DO NOT insert to table if symbol was undefined + if (Flags & llvm::object::SymbolRef::SF_Undefined) + continue; + + // Note, we are at last resort and loading library based on a weak + // symbol is allowed. Otherwise, the JIT will issue an unresolved + // symbol error. + // + // There are other weak symbol kinds (marked as 'V') to denote + // typeinfo and vtables. It is unclear whether we should load such + // libraries or from which library we should resolve the symbol. + // We seem to not have a way to differentiate it from the symbol API. + + llvm::Expected SymNameErr = S.getName(); + if (!SymNameErr) { + Warning("TCling__FindSymbol", "Failed to read symbol %s", mangled_name.c_str()); + continue; + } + + if (SymNameErr.get() == mangled_name) + return true; + } + + if (!BinObjFile->isELF()) return false; - auto Symbols = RealSoFile->symbols(); - for (auto S : Symbols) { + // ELF file format has .dynstr section for the dynamic symbol table. + const auto *ElfObj = cast(BinObjFile); + + for (const auto &S : ElfObj->getDynamicSymbolIterators()) { uint32_t Flags = S.getFlags(); // DO NOT insert to table if symbol was undefined if (Flags & llvm::object::SymbolRef::SF_Undefined) @@ -6138,7 +6179,7 @@ static bool LookupNormalSymbols(llvm::object::ObjectFile *RealSoFile, const std: llvm::Expected SymNameErr = S.getName(); if (!SymNameErr) { - Warning("LookupNormalSymbols", "Failed to read symbol"); + Warning("TCling__FindSymbol", "Failed to read symbol %s", mangled_name.c_str()); continue; } @@ -6149,9 +6190,10 @@ static bool LookupNormalSymbols(llvm::object::ObjectFile *RealSoFile, const std: return false; } -static void* LazyFunctionCreatorAutoloadForModule(const std::string& mangled_name, - cling::Interpreter *fInterpreter) { - using namespace llvm::object; +static std::string ResolveSymbol(const std::string& mangled_name, + cling::Interpreter *interp, + bool searchSystem = true) { + assert(!mangled_name.empty()); using namespace llvm::sys::path; using namespace llvm::sys::fs; @@ -6159,106 +6201,100 @@ static void* LazyFunctionCreatorAutoloadForModule(const std::string& mangled_nam static bool sFirstRun = true; static bool sFirstSystemLibrary = true; - // sLibraies contains pair of sPaths[i] (eg. /home/foo/module) and library name (eg. libTMVA.so). The - // reason why we're separating sLibraries and sPaths is that we have a lot of - // dupulication in path, for example we have "/home/foo/module-release/lib/libFoo.so", "/home/../libBar.so", "/home/../lib.." - // and it's waste of memory to store the full path. - static std::vector< std::pair > sLibraries; - static std::vector sPaths; + // LibraryPath contains a pair offset to the canonical dirname (stored as + // sPaths[i]) and a filename. For example, `/home/foo/root/lib/libTMVA.so`, + // the .first will contain an index in sPaths where `/home/foo/root/lib/` + // will be stored and .second `libTMVA.so`. + // This approach reduces the duplicate paths as at one location there may be + // plenty of libraries. + using LibraryPath = std::pair; + using LibraryPaths = std::vector; + using BasePaths = std::vector; + static LibraryPaths sLibraries; + static BasePaths sPaths; + static LibraryPaths sQueriedLibraries; // For system header autoloading - static std::vector< std::pair > sSysLibraries; - static std::vector sSysPaths; + static LibraryPaths sSysLibraries; if (sFirstRun) { - TCling__FindLoadedLibraries(sLibraries, sPaths, *fInterpreter, /* searchSystem */ false); + TCling__FindLoadedLibraries(sLibraries, sPaths, *interp, /* searchSystem */ false); sFirstRun = false; } - // The JIT gives us a mangled name which has only one leading underscore on - // all platforms, for instance _ZN8TRandom34RndmEv. However, on OSX the - // linker stores this symbol as __ZN8TRandom34RndmEv (adding an extra _). -#ifdef R__MACOSX - std::string name_in_so = "_" + mangled_name; -#else - std::string name_in_so = mangled_name; -#endif - - - // Iterate over files under this path. We want to get each ".so" files - for (std::pair &P : sLibraries) { - llvm::SmallString<400> Vec(sPaths[P.first]); + auto GetLibFileName = [](const LibraryPath &P, const BasePaths &BaseP) { + llvm::SmallString<512> Vec(BaseP[P.first]); llvm::sys::path::append(Vec, StringRef(P.second)); - const std::string LibName = Vec.str(); - - auto SoFile = ObjectFile::createObjectFile(LibName); - if (!SoFile) - continue; + return Vec.str().str(); + }; - if (LookupNormalSymbols(SoFile.get().getBinary(), name_in_so, LibName)) { - if (gSystem->Load(LibName.c_str(), "", false) < 0) - Error("LazyFunctionCreatorAutoloadForModule", "Failed to load library %s", LibName.c_str()); + if (!sQueriedLibraries.empty()) { + // Last call we were asked if a library contains a symbol. Usually, the + // caller wants to load this library. Check if was loaded and remove it + // from our lists of not-yet-loaded libs. + for (const LibraryPath &P : sQueriedLibraries) { + const std::string LibName = GetLibFileName(P, sPaths); + if (!gCling->IsLibraryLoaded(LibName.c_str())) + continue; - // We want to delete a loaded library from sLibraries cache, because sLibraries is - // a vector of candidate libraries which might be loaded in the future. sLibraries.erase(std::remove(sLibraries.begin(), sLibraries.end(), P), sLibraries.end()); - void* addr = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(mangled_name.c_str()); - return addr; + if (!sSysLibraries.empty()) + sSysLibraries.erase(std::remove(sSysLibraries.begin(), sSysLibraries.end(), P), sSysLibraries.end()); } } - // Normal lookup failed! Fall back to system library - if (sFirstSystemLibrary) { - TCling__FindLoadedLibraries(sSysLibraries, sSysPaths, *fInterpreter, /* searchSystem */ true); - sFirstSystemLibrary = false; + if (sFirstRun) { + TCling__FindLoadedLibraries(sLibraries, sPaths, *interp, /* searchSystem */ false); + sFirstRun = false; } - for (std::pair &P : sSysLibraries) { - llvm::SmallString<400> Vec(sSysPaths[P.first]); - llvm::sys::path::append(Vec, StringRef(P.second)); - const std::string LibName = Vec.str(); + // Iterate over files under this path. We want to get each ".so" files + for (const LibraryPath &P : sLibraries) { + const std::string LibName = GetLibFileName(P, sPaths); - auto SoFile = ObjectFile::createObjectFile(LibName); - if (!SoFile) - continue; + // FIXME: We should also iterate over the dynamic symbols for ROOT + // libraries. However, it seems to be redundant for the moment as we do + // not strictly require symbols from those sections. Enable after checking + // performance! + if (FindSymbol(LibName, mangled_name)) { + sQueriedLibraries.push_back(P); + return LibName; + } + } - auto RealSoFile = SoFile.get().getBinary(); + if (!searchSystem) + return ""; - if (LookupNormalSymbols(RealSoFile, name_in_so, LibName)) { - if (gSystem->Load(LibName.c_str(), "", false) < 0) - Error("LazyFunctionCreatorAutoloadForModule", "Failed to load library %s", LibName.c_str()); + // Lookup in non-system libraries failed. Expand the search to the system. + if (sFirstSystemLibrary) { + TCling__FindLoadedLibraries(sSysLibraries, sPaths, *interp, /* searchSystem */ true); + sFirstSystemLibrary = false; + } - sSysLibraries.erase(std::remove(sSysLibraries.begin(), sSysLibraries.end(), P), sSysLibraries.end()); - void* addr = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(mangled_name.c_str()); - return addr; - } + for (const LibraryPath &P : sSysLibraries) { + const std::string LibName = GetLibFileName(P, sPaths); - // Lookup for dynamic symbols - auto sections = RealSoFile->sections(); - for (auto section : sections) { - llvm::StringRef sectionName; - section.getName(sectionName); - - // .dynstr contains string of dynamic symbols - if (sectionName == ".dynstr") { - llvm::StringRef dContents; - section.getContents(dContents); - // If this library contains mangled name - if (dContents.contains(mangled_name)) { - if (gSystem->Load(LibName.c_str(), "", false) < 0) - Error("LazyFunctionCreatorAutoloadForModule", "Failed to load library %s", LibName.c_str()); - - // Delete a loaded library from sLibraries cache. - sSysLibraries.erase(std::remove(sSysLibraries.begin(), sSysLibraries.end(), P), sSysLibraries.end()); - void* addr = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(mangled_name.c_str()); - return addr; - } - } + if (FindSymbol(LibName, mangled_name)) { + sQueriedLibraries.push_back(P); + return LibName; } } - // Lookup failed!!!! - return nullptr; + return ""; // Search found no match. +} + +static void* LazyFunctionCreatorAutoloadForModule(const std::string& mangled_name, + cling::Interpreter *interp) { + std::string LibName = ResolveSymbol(mangled_name, interp); + if (LibName.empty()) + return nullptr; + + if (gSystem->Load(LibName.c_str(), "", false) < 0) + Error("TCling__LazyFunctionCreatorAutoloadForModule", + "Failed to load library %s", LibName.c_str()); + + void* addr = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(mangled_name.c_str()); + return addr; } //////////////////////////////////////////////////////////////////////////////// @@ -6919,14 +6955,90 @@ const char* TCling::GetClassSharedLibs(const char* cls) return 0; } +/// This interface returns a list of dependent libraries in the form: +/// lib libA.so libB.so libC.so. The first library is the library we are +/// searching dependencies for. +/// Note: In order to speed up the search, we display the dependencies of the +/// libraries which are not yet loaded. For instance, if libB.so was already +/// loaded the list would contain: lib libA.so libC.so. +static std::string GetSharedLibImmediateDepsSlow(std::string lib, + cling::Interpreter *interp, + bool skipLoadedLibs = true) +{ + TString LibFullPath(lib); + if (!llvm::sys::path::is_absolute(lib)) { + if (!gSystem->FindDynamicLibrary(LibFullPath, /*quiet=*/true)) { + Error("TCling__GetSharedLibImmediateDepsSlow", "Cannot find library '%s'", lib.c_str()); + return ""; + } + } else { + lib = llvm::sys::path::filename(lib); + } + + auto ObjF = llvm::object::ObjectFile::createObjectFile(LibFullPath.Data()); + if (!ObjF) { + Warning("TCling__GetSharedLibImmediateDepsSlow", "Failed to read object file %s", lib.c_str()); + return ""; + } + + llvm::object::ObjectFile *BinObjFile = ObjF.get().getBinary(); + + std::set DedupSet; + std::string Result = lib + ' '; + for (const auto &S : BinObjFile->symbols()) { + uint32_t Flags = S.getFlags(); + if (Flags & llvm::object::SymbolRef::SF_Undefined) { + llvm::Expected SymNameErr = S.getName(); + if (!SymNameErr) { + Warning("GetSharedLibDepsForModule", "Failed to read symbol"); + continue; + } + llvm::StringRef SymName = SymNameErr.get(); + if (SymName.empty()) + continue; + + // If we can find the address of the symbol, we have loaded it. Skip. + if (skipLoadedLibs && llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(SymName)) + continue; + + std::string found = ResolveSymbol(SymName, interp, /*searchSystem*/false); + // The expected output is just filename without the full path, which + // is not very accurate, because our Dyld implementation might find + // a match in location a/b/c.so and if we return just c.so ROOT might + // resolve it to y/z/c.so and there we might not be ABI compatible. + // FIXME: Teach the users of GetSharedLibDeps to work with full paths. + if (!found.empty()) { + std::string cand = llvm::sys::path::filename(found).str(); + if (!DedupSet.insert(cand).second) + continue; + + Result += cand + ' '; + } + } + } + + return Result; +} + + //////////////////////////////////////////////////////////////////////////////// /// Get the list a libraries on which the specified lib depends. The /// returned string contains as first element the lib itself. /// Returns 0 in case the lib does not exist or does not have -/// any dependencies. +/// any dependencies. If useDyld is true, we iterate through all available +/// libraries and try to construct the dependency chain by resolving each +/// symbol. -const char* TCling::GetSharedLibDeps(const char* lib) +const char* TCling::GetSharedLibDeps(const char* lib, bool useDyld/* = false*/) { + if (useDyld) { + std::string libs = GetSharedLibImmediateDepsSlow(lib, fInterpreter); + if (!libs.empty()) { + fAutoLoadLibStorage.push_back(libs); + return fAutoLoadLibStorage.back().c_str(); + } + } + if (!fMapfile || !lib || !lib[0]) { return 0; } diff --git a/core/metacling/src/TCling.h b/core/metacling/src/TCling.h index 05b28fdbe4d94..f607ee00d0833 100644 --- a/core/metacling/src/TCling.h +++ b/core/metacling/src/TCling.h @@ -201,7 +201,7 @@ class TCling final : public TInterpreter { char* GetPrompt() { return fPrompt; } const char* GetSharedLibs(); const char* GetClassSharedLibs(const char* cls); - const char* GetSharedLibDeps(const char* lib); + const char* GetSharedLibDeps(const char* lib, bool tryDyld = false); const char* GetIncludePath(); virtual const char* GetSTLIncludePath() const; TObjArray* GetRootMapFiles() const { return fRootmapFiles; } diff --git a/core/metacling/src/TClingCallbacks.cxx b/core/metacling/src/TClingCallbacks.cxx index 57c8f3a268506..7dcaf7fbd574f 100644 --- a/core/metacling/src/TClingCallbacks.cxx +++ b/core/metacling/src/TClingCallbacks.cxx @@ -887,15 +887,16 @@ static void SearchAndAddPath(const std::string& Path, DirIt != DirEnd && !EC; DirIt.increment(EC)) { std::string FileName(DirIt->path()); - if (!llvm::sys::fs::is_directory(FileName) && llvm::sys::path::extension(FileName) == ".so") { - // TCling::IsLoaded is incredibly slow! - // No need to check linked libraries, as this function is only invoked - // for symbols that cannot be found (neither by dlsym nor in the JIT). - if (dyLibManager->isLibraryLoaded(FileName.c_str())) - continue; - sLibraries.push_back(std::make_pair(sPaths.size(), llvm::sys::path::filename(FileName))); - flag = true; - } + if (llvm::sys::fs::is_directory(FileName)) + continue; + if (!cling::DynamicLibraryManager::isSharedLibrary(FileName)) + continue; + // No need to check linked libraries, as this function is only invoked + // for symbols that cannot be found (neither by dlsym nor in the JIT). + if (dyLibManager->isLibraryLoaded(FileName.c_str())) + continue; + sLibraries.push_back(std::make_pair(sPaths.size(), llvm::sys::path::filename(FileName))); + flag = true; } if (flag) @@ -915,20 +916,12 @@ void TCling__FindLoadedLibraries(std::vector> & { // Store the information of path so that we don't have to iterate over the same path again and again. static std::unordered_set alreadyLookedPath; - const clang::Preprocessor &PP = interpreter.getCI()->getPreprocessor(); - const HeaderSearchOptions &HSOpts = PP.getHeaderSearchInfo().getHeaderSearchOpts(); cling::DynamicLibraryManager* dyLibManager = interpreter.getDynamicLibraryManager(); - if (searchSystem) { - llvm::SmallVector systemPath = dyLibManager->getSystemSearchPath(); - for (const std::string& sysPath : systemPath) { - SearchAndAddPath(sysPath, sLibraries, sPaths, alreadyLookedPath, dyLibManager); - } - } else { - const std::vector& MPaths = HSOpts.PrebuiltModulePaths; - // Take path here eg. "/home/foo/module-release/lib/" - for (const std::string& mPath : MPaths) { - SearchAndAddPath(mPath, sLibraries, sPaths, alreadyLookedPath, dyLibManager); - } + const auto &searchPaths = dyLibManager->getSearchPath(); + for (const cling::DynamicLibraryManager::SearchPathInfo &Info : searchPaths) { + if (!Info.IsUser && !searchSystem) + continue; + SearchAndAddPath(Info.Path, sLibraries, sPaths, alreadyLookedPath, dyLibManager); } } diff --git a/core/metacling/test/TClingTests.cxx b/core/metacling/test/TClingTests.cxx index 7d058be99572c..3d6462f4f98f9 100644 --- a/core/metacling/test/TClingTests.cxx +++ b/core/metacling/test/TClingTests.cxx @@ -133,19 +133,18 @@ static std::string MakeLibNamePlatformIndependent(llvm::StringRef libName) EXPECT_TRUE(llvm::sys::path::has_extension(libName)); libName.consume_front("lib"); // Remove the extension. - return libName.substr(0, libName.find_last_of('.')).str(); -} - -// Shortens the invocation. -static const char *GetLibs(const char *cls) -{ - return gInterpreter->GetClassSharedLibs(cls); + return libName.substr(0, libName.find_first_of('.')).str(); } // Check if the heavily used interface in TCling::AutoLoad returns consistent // results. TEST_F(TClingTests, GetClassSharedLibs) { + // Shortens the invocation. + auto GetLibs = [](const char *cls) -> const char * { + return gInterpreter->GetClassSharedLibs(cls); + }; + llvm::StringRef lib = GetLibs("TLorentzVector"); ASSERT_STREQ("Physics", MakeLibNamePlatformIndependent(lib).c_str()); @@ -179,3 +178,50 @@ TEST_F(TClingTests, GetClassSharedLibs) // != GetLibs("ROOT::Math::LorentzVector>") // note the missing space. } + +static std::string MakeDepLibsPlatformIndependent(llvm::StringRef libs) { + llvm::SmallVector splitLibs; + libs.trim().split(splitLibs, ' '); + assert(!splitLibs.empty()); + std::string result = MakeLibNamePlatformIndependent(splitLibs[0]) + ' '; + splitLibs.erase(splitLibs.begin()); + + std::sort(splitLibs.begin(), splitLibs.end()); + for (llvm::StringRef lib : splitLibs) + result += MakeLibNamePlatformIndependent(lib.trim()) + ' '; + + return llvm::StringRef(result).rtrim(); +} + +// Check the interface computing the dependencies of a given library. +TEST_F(TClingTests, GetSharedLibDeps) +{ + // Shortens the invocation. + auto GetLibDeps = [](const char *lib) -> const char* { + return gInterpreter->GetSharedLibDeps(lib, /*tryDyld*/true); + }; + + std::string SeenDeps + = MakeDepLibsPlatformIndependent(GetLibDeps("libGenVector.so")); +#ifdef R__MACOSX + ASSERT_TRUE(llvm::StringRef(SeenDeps).startswith("GenVector New")); +#else + // Depends only on libCore.so but libCore.so is loaded and thus missing. + ASSERT_STREQ("GenVector", SeenDeps.c_str()); +#endif + + SeenDeps = MakeDepLibsPlatformIndependent(GetLibDeps("libTreePlayer.so")); + llvm::StringRef SeenDepsRef = SeenDeps; + + // Depending on the configuration we expect: + // TreePlayer Gpad Graf Graf3d Hist [Imt] MathCore MultiProc Net [New] Tree [tbb].. + // FIXME: We should add a generic gtest regex matcher and use a regex here. + ASSERT_TRUE(SeenDepsRef.startswith("TreePlayer Gpad Graf Graf3d Hist")); + ASSERT_TRUE(SeenDepsRef.contains("MathCore MultiProc Net")); + ASSERT_TRUE(SeenDepsRef.contains("Tree")); + + EXPECT_ROOT_ERROR(ASSERT_TRUE(nullptr == GetLibDeps("")), + "Error in : Cannot find library ''\n"); + EXPECT_ROOT_ERROR(ASSERT_TRUE(nullptr == GetLibDeps(" ")), + "Error in : Cannot find library ' '\n"); +} diff --git a/interpreter/cling/include/cling/Interpreter/DynamicLibraryManager.h b/interpreter/cling/include/cling/Interpreter/DynamicLibraryManager.h index 3dcaa47e3b66e..fc7ae4f318bc4 100644 --- a/interpreter/cling/include/cling/Interpreter/DynamicLibraryManager.h +++ b/interpreter/cling/include/cling/Interpreter/DynamicLibraryManager.h @@ -34,6 +34,16 @@ namespace cling { kLoadLibNumResults }; + /// Describes the library search paths. + struct SearchPathInfo { + /// The search path. + /// + std::string Path; + + /// True if the Path is on the LD_LIBRARY_PATH. + /// + bool IsUser; + }; private: typedef const void* DyLibHandle; typedef llvm::DenseMap DyLibs; @@ -48,7 +58,7 @@ namespace cling { ///\brief System's include path, get initialized at construction time. /// - llvm::SmallVector m_SystemSearchPaths; + llvm::SmallVector m_SearchPaths; InterpreterCallbacks* m_Callbacks; @@ -81,8 +91,8 @@ namespace cling { /// ///\returns System include paths. /// - llvm::SmallVector getSystemSearchPath() { - return m_SystemSearchPaths; + const llvm::SmallVectorImpl& getSearchPath() { + return m_SearchPaths; } ///\brief Looks up a library taking into account the current include paths diff --git a/interpreter/cling/lib/Interpreter/DynamicLibraryManager.cpp b/interpreter/cling/lib/Interpreter/DynamicLibraryManager.cpp index 27e79d58890e0..c1900f1066819 100644 --- a/interpreter/cling/lib/Interpreter/DynamicLibraryManager.cpp +++ b/interpreter/cling/lib/Interpreter/DynamicLibraryManager.cpp @@ -50,28 +50,34 @@ namespace cling { SplitPaths(Env, CurPaths, utils::kPruneNonExistant, platform::kEnvDelim, Opts.Verbose()); for (const auto& Path : CurPaths) - m_SystemSearchPaths.push_back(Path.str()); + m_SearchPaths.push_back({Path.str(), /*IsUser*/true}); } } - platform::GetSystemLibraryPaths(m_SystemSearchPaths); + llvm::SmallVector SysPaths; + platform::GetSystemLibraryPaths(SysPaths); + + for (const std::string& P : SysPaths) + m_SearchPaths.push_back({P, /*IsUser*/false}); // This will currently be the last path searched, should it be pushed to // the front of the line, or even to the front of user paths? - m_SystemSearchPaths.push_back("."); + m_SearchPaths.push_back({".", /*IsUser*/true}); } DynamicLibraryManager::~DynamicLibraryManager() {} std::string DynamicLibraryManager::lookupLibInPaths(llvm::StringRef libStem) const { - llvm::SmallVector - Paths(m_Opts.LibSearchPath.begin(), m_Opts.LibSearchPath.end()); - Paths.append(m_SystemSearchPaths.begin(), m_SystemSearchPaths.end()); + llvm::SmallVector Paths; + for (const std::string &P : m_Opts.LibSearchPath) + Paths.push_back({P, /*IsUser*/true}); + + Paths.append(m_SearchPaths.begin(), m_SearchPaths.end()); - for (llvm::SmallVectorImpl::const_iterator - IPath = Paths.begin(), E = Paths.end();IPath != E; ++IPath) { - llvm::SmallString<512> ThisPath(*IPath); // FIXME: move alloc outside loop + llvm::SmallString<512> ThisPath; + for (const SearchPathInfo& Info : Paths) { + ThisPath = Info.Path; llvm::sys::path::append(ThisPath, libStem); bool exists; if (isSharedLibrary(ThisPath.str(), &exists))