Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions core/base/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -211,15 +211,9 @@ if(ROOT_NEED_STDCXXFS)
target_link_libraries(Core PRIVATE stdc++fs)
endif()

# This code about the LIB_CORE_NAME define is important for TROOT::GetLibDir()
get_target_property(core_prefix Core PREFIX)
get_target_property(core_suffix Core SUFFIX)
get_target_property(core_soversion Core SOVERSION)
if(core_soversion)
set(full_core_filename "${core_prefix}Core${core_suffix}.${core_soversion}")
else()
set(full_core_filename "${core_prefix}Core${core_suffix}")
endif()
# This code about the LIB_CORE_NAME define is important for TROOT::GetSharedLibDir()
set(full_core_filename $<TARGET_FILE_NAME:Core>)

target_compile_options(Core PRIVATE -DLIB_CORE_NAME=${full_core_filename})

if(PCRE2_FOUND)
Expand Down
132 changes: 115 additions & 17 deletions core/base/src/TROOT.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -3016,6 +3016,33 @@ const TString& TROOT::GetBinDir() {
////////////////////////////////////////////////////////////////////////////////
/// Get the library directory in the installation. Static utility function.
///
/// By default, this is just an alias for TROOT::GetSharedLibDir(), which
/// returns the directory containing the ROOT shared libraries.
///
/// On Windows, the behavior is different. In that case, this function doesn't
/// return the directory of the **shared libraries** (like `libCore.dll`), but
/// the **import libraries**, which are used at link time (like `libCore.lib`).

const TString &TROOT::GetLibDir()
{
#if defined(R__WIN32)
static bool initialized = false;
static TString rootlibdir;
if (initialized)
return rootlibdir;

initialized = true;
rootlibdir = "lib";
gSystem->PrependPathName(GetRootSys(), rootlibdir);
return rootlibdir;
#else
return TROOT::GetSharedLibDir();
#endif
}

////////////////////////////////////////////////////////////////////////////////
/// Get the shared libraries directory in the installation. Static utility function.
///
/// This function inspects the libraries currently loaded in the process to
/// locate the ROOT Core library. Once found, it extracts and returns the
/// directory containing that library. If the ROOT Core library was not found,
Expand All @@ -3024,9 +3051,9 @@ const TString& TROOT::GetBinDir() {
/// The result is cached in a static variable so the lookup is only performed
/// once per process, and the implementation is platform-specific.
///
/// \return The directory path (as a `TString`) containing the ROOT core library.
/// \return The directory path (as a `TString`) containing the ROOT shared libraries.

const TString &TROOT::GetLibDir()
const TString &TROOT::GetSharedLibDir()
{
static bool haveLooked = false;
static TString rootlibdir;
Expand Down Expand Up @@ -3056,9 +3083,72 @@ const TString &TROOT::GetLibDir()

#elif defined(_WIN32)

// Or Windows, the original hardcoded path is kept for now.
rootlibdir = "lib";
gSystem->PrependPathName(GetRootSys(), rootlibdir);
HMODULE modulesStack[1024];
std::vector<HMODULE> modulesHeap;
HMODULE *modules = modulesStack;
DWORD needed = 0;

HANDLE process = GetCurrentProcess();

bool success = EnumProcessModules(process, modulesStack, sizeof(modulesStack), &needed);

// It is recommended in the API documentation to check if the output array
// was too small, and if yes, call EnumProcessModules again with an array of
// the required size. To avoid heap allocations, we use a heap array only
// when the number of modules was too large for the original stack array.
// See: https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodules#remarks
if (needed > sizeof(modulesStack)) {
modulesHeap.resize(needed / sizeof(HMODULE));
success = EnumProcessModules(process, modulesHeap.data(), needed, &needed);
modules = modulesHeap.data();
}

if (success) {
const unsigned int count = needed / sizeof(HMODULE);

for (unsigned int i = 0; i < count; ++i) {
wchar_t wpath[MAX_PATH];
DWORD len = GetModuleFileNameW(modules[i], wpath, MAX_PATH);
if (!len)
continue;

// According to the Windows API documentation, there are exceptions
// where a path can be longer than MAX_PATH:
// https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
// In case that happens here, we print an error message.
if (len == MAX_PATH) {
// Convert UTF-16 path to UTF-8 for the error message
int utf8len = WideCharToMultiByte(CP_UTF8, 0, wpath, -1, nullptr, 0, nullptr, nullptr);

std::string utf8path(utf8len - 1, '\0');
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, utf8path.data(), utf8len, nullptr, nullptr);

utf8path += "... [TRUNCATED]";

::Error("TROOT::GetSharedLibDir",
"Module path \"%s\" exceeded maximum path length of %u characters! "
"ROOT might not be able to resolve its resource directories.",
utf8path.c_str(), MAX_PATH);

continue;
}

fs::path p{wpath};
if (p.filename() == TO_LITERAL(LIB_CORE_NAME)) {

// Convert UTF-16 to UTF-8 explicitly
const std::wstring wdir = p.parent_path().wstring();

int utf8len = WideCharToMultiByte(CP_UTF8, 0, wdir.c_str(), -1, nullptr, 0, nullptr, nullptr);

std::string utf8dir(utf8len - 1, '\0');
WideCharToMultiByte(CP_UTF8, 0, wdir.c_str(), -1, utf8dir.data(), utf8len, nullptr, nullptr);

rootlibdir = utf8dir.c_str();
break;
}
}
}

#else

Expand All @@ -3069,7 +3159,26 @@ const TString &TROOT::GetLibDir()

fs::path p = info->dlpi_name;
if (p.filename() == TO_LITERAL(LIB_CORE_NAME)) {
libdir = p.parent_path().c_str();
std::error_code ec;

// Resolve symlinks: critical for environments like CMSSW, where the
// ROOT libraries are loaded via symlinks that point to the actual
// install directory
fs::path resolved = fs::canonical(p, ec);
if (ec) {
::Error("TROOT",
"Failed to canonicalize detected ROOT shared library path:\n"
"%s\n"
"Error code: %d (%s)\n"
"Error category: %s\n"
"This is an unexpected internal error and ROOT might not work.\n"
"Please report this issue on GitHub: https://github.com/root-project/root/issues",
p.string().c_str(), ec.value(), ec.message().c_str(), ec.category().name());
// Fall back to the loader path if canonicalization fails. The path
// will likely be wrong, but at least not garbage
resolved = p;
}
libdir = resolved.parent_path().c_str();
return 1; // stop iteration
}
return 0; // continue
Expand All @@ -3082,17 +3191,6 @@ const TString &TROOT::GetLibDir()
return rootlibdir;
}

////////////////////////////////////////////////////////////////////////////////
/// Get the shared libraries directory in the installation. Static utility function.

const TString& TROOT::GetSharedLibDir() {
#if defined(R__WIN32)
return TROOT::GetBinDir();
#else
return TROOT::GetLibDir();
#endif
}

////////////////////////////////////////////////////////////////////////////////
/// Get the include directory in the installation. Static utility function.

Expand Down
6 changes: 6 additions & 0 deletions core/base/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ ROOT_ADD_GTEST(CoreBaseTests
TROOTTests.cxx
LIBRARIES RIO Core)

# For TROOTTests.cxx:
if(ROOT_NEED_STDCXXFS)
target_link_libraries(CoreBaseTests PRIVATE stdc++fs)
endif()
target_compile_options(CoreBaseTests PRIVATE -DEXPECTED_SHARED_LIBRARY_DIR="${localruntimedir}")

ROOT_ADD_GTEST(CoreErrorTests TErrorTests.cxx LIBRARIES Core)

ROOT_ADD_GTEST(CoreSystemTests TSystemTests.cxx LIBRARIES Core)
Expand Down
23 changes: 21 additions & 2 deletions core/base/test/TROOTTests.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

#include "TROOT.h"

#include <string>
#include <filesystem>
#include <sstream>
#include <string>
#include <iostream>

TEST(TROOT, Version)
{
Expand All @@ -21,4 +23,21 @@ TEST(TROOT, Version)
EXPECT_EQ(refLength[tokCounter++], buf.size());
}
EXPECT_EQ(3, tokCounter);
}
}

// TROOT::GetSharedLibDir() is fundamental to resolve all the directories
// relevant for ROOT, because it can be inferred without environment variables
// like ROOTSYS by locating libCore, which is loaded by definition when using
// ROOT. Therefore, GetSharedLibDir() serves as an anchor to resolve all other
// directories, using the correct relative paths for either the build or
// install tree. Given this fundamental role, we need to test that it works.
TEST(TROOT, GetSharedLibDir)
{
namespace fs = std::filesystem;

// Use std::filesystem for automatic path normalization.
fs::path libDir = gROOT->GetSharedLibDir().Data();
fs::path libDirRef = EXPECTED_SHARED_LIBRARY_DIR;

EXPECT_EQ(libDir, libDirRef);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reasons this seems to fail ...

}
7 changes: 0 additions & 7 deletions tutorials/machine_learning/TMVA_SOFIE_GNN_Application.C
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,3 @@ void TMVA_SOFIE_GNN_Application (bool verbose = false)
c2->cd(3); o3->Draw();

}

int main () {

TMVA_SOFIE_GNN_Application();
}


Loading