Using a dictionary without gSystem->Load, Mac OS version

Continuing the discussion from Using a dictionary without gSystem->Load:

In that previous discussion, I asked about how to use ROOT_GENERATE_DICTIONARY with the correct linker options to get a dictionary to load without using gSystem->Load. As you can see in that discussion, @pcanal supplied the answer: I needed the ld option -Wl,–no-as-needed to get the library to load at execution time.

That worked fine on AlmaLinux. But I’m a glutton for punishment. Now I’m trying to get the procedure to work on Mac OS X.

After much pain, I manated to get the dictionary to compile and link. However, the ld command on Mac OS X Darwin does not accept the --no-as-needed option. That’s fine; it’s easy to test in a CMakeLists.txt file if I’m running in Darwin and omit that option.

The problem is that the problem I brought up in the original discussion returns: Even though I see the dictionary .dylib file included in the linker line generated by cmake, when the program executes I get the error messages that say the library isn’t loaded.

Is there some option to solve this issue on Mac OS X?

Hi,

Thanks for the post. You have at least three options:

  1. link the dictionary lib to your program or to the library which contains the classes for which you generated the dictionary
  2. build the dictionary in the same library where the classes for which the dictionaries are done
  3. rely on ROOT’s autoloading mechanism. If you generated pcms, you should not worry, if you did not generate pcm for your dictionaries you should use a rootmap. The keys for autoloading are the classes you selected. Autoloading means that at runtime, ROOT will load the needed libraries based on the autoloading keys.

Best,
D

Hi @Danilo! I thought I was doing all three of those things, but it turns out that I wasn’t on Mac OS X. On Linux, the .pcm file was being created. On Mac OS X, it was not!

Here are the details.

On the Linux side, these are the CMakeLists.txt lines in the dictionary directory:

file(GLOB headerList ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
file(GLOB DataObjSrc ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc )

set(LinkDef ${CMAKE_CURRENT_SOURCE_DIR}/include/LinkDef.hh)
set(Dictionary ${CMAKE_CURRENT_BINARY_DIR}/DataObjDict)
set(LibName ${CMAKE_PROJECT_NAME}DataObj)

ROOT_GENERATE_DICTIONARY ( ${Dictionary} ${headerList} LINKDEF ${LinkDef} )

add_library (${LibName} SHARED ${Dictionary}.cxx ${DataObjSrc})
target_include_directories (${LibName} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

This works in Linux (up to the minor build-order issue I reported earlier).

In Mac OS X, the above lines don’t work. When I try to build the project, I got an error:

Undefined symbols for architecture arm64:
  "Fatal(char const*, char const*, ...)", referenced from:
      ROOT::Detail::TCollectionProxyInfo::Iterators<std::__1::vector<int, std::__1::allocator<int>>, false>::next(void*, void const*) in DataObjDict.cxx.o

followed by many more “referenced from” lines.

I did my due diligence to track this down, and finally found this topic in which @pcanal was able to solve a similar problem. Following the recipe in that post, I changed some CMakeLists.txt lines on Mac OS X:

The key lines on Linux:

ROOT_GENERATE_DICTIONARY ( ${Dictionary} ${headerList} LINKDEF ${LinkDef} )
add_library (${LibName} SHARED ${Dictionary}.cxx ${DataObjSrc})

The revised lines on Mac OS X:

ROOT_GENERATE_DICTIONARY ( ${Dictionary} ${headerList}
                         MODULE ${LibName}
                         LINKDEF ${LinkDef} )
add_library (${LibName} SHARED ${DataObjSrc})

In other words, to get the code to compile and link on Mac OS X, I had to add MODULE ${LibName} to ROOT_GENERATE_DICTIONARY; then I had to remove ${Dictionary}.cxx from the shared library dependencies; otherwise I got the error:

make[2]: *** No rule to make target `GramsDataObj/MODULE', needed by `GramsDataObj/DataObjDict.cxx'.  Stop.

But the above lines don’t generate the .pcm file (when I tried, they don’t generate it on Linux either). As a result, I get the same issue I reported before: When the program executes, it can’t find the dictionary.

Can you spot what I’m missing?

I should add:

In the CMakeLists.txt files of the programs that depend on the dictionary, I have:

target_link_libraries(${PROG} ${CMAKE_PROJECT_NAME}DataObj)
# The following line is needed to make sure DataObj is linked with the executable.
if( NOT MACOSX )
    target_link_options(${PROG} PRIVATE "LINKER:-no-as-needed")
endif()

It was another whole web-search investigation for me to discover that, due to binary-file differences between Linux and Mac OS, the -Wl,--no-as-needed option does not work for ld on Darwin.

I’m one step closer.

I found that adding the following line enabled compilation and linking on Mac OS X, but has no effect on the Linux process:

link_libraries (${ROOT_LIBRARIES})

In context, the key lines now read:

set(LibName ${CMAKE_PROJECT_NAME}DataObj)
ROOT_GENERATE_DICTIONARY ( ${Dictionary} ${headerList} LINKDEF ${LinkDef} )
link_libraries (${ROOT_LIBRARIES})
add_library (${LibName} SHARED ${Dictionary}.cxx ${DataObjSrc})

The .pcm file is generated.

However, I’m back to where I was in the earlier problem. When I try to execute the program, it behaves as if it can’t find the dictionary library:

Error in <TTree::Branch>: The pointer specified for EventID is not of a class known to ROOT
Error in <TTree::Branch>: The class requested (map<int,grams::MCTrack>) for the branch "TrackList" is an instance of an stl collection and does not have a compiled CollectionProxy. Please generate the dictionary for this collection (map<int,grams::MCTrack>) to avoid to write corrupted data.

I’m in search of the “magical” cmake invocation that does what -Wl,--no-as-needed did in Linux.

To follow-up a bit, though still with no solution:

I can load the dictionary manually in both interactive ROOT and in Python:

# root
root [0] gSystem->AddIncludePath("../GramsSim/GramsDataObj/include");
root [1] gSystem->Load("./libDictionary.dylib");
event = ROOT.grams.EventID(4,3)
root [3] cout << event << endl
# python
>>> import ROOT
>>> ROOT.gSystem.Load("./libDictionary.dylib")
>>> event = ROOT.grams.EventID(4,3)
>>> event.Event()

The dictionary itself is fine. It’s the process of binding/loading the library to the executable during execution that’s not working.

Whatever the problem is, it’s not (directly) due to ROOT_GENERATE_DICTIONARY. I tried to just compile a program on Mac OS directly:

g++ -o dEdxExample dEdxExample.cc `root-config --cflags --libs` \
   -I../GramsG4/GramsDataObj/include \
   ./libDictionary.dylib

The Linux equivalent compiles and runs without any problems. But this above command, while it compiles dEdxExample without any errors, can’t find the dictionary when it executes:

# ./dEdxExample 
Warning in <TClass::Init>: no dictionary for class grams::EventID is available
Warning in <TClass::Init>: no dictionary for class grams::MCTrack is available
Warning in <TClass::Init>: no dictionary for class grams::MCLArHit is available
Warning in <TClass::Init>: no dictionary for class grams::MCScintHit is available
Warning in <TClass::Init>: no dictionary for class grams::MCTrajectoryPoint is available

 *** Break *** segmentation violation

To confirm that the files are there:

# ls -lh *Dict*
-rw-r--r--@ 1 seligman  staff   4.3K Jun 28 09:03 Dictionary_rdict.pcm
-rwxr-xr-x@ 1 seligman  staff    56K Jun 28 11:49 libDictionary.dylib

Any thoughts?

I continue to chip away at the problem.

On Linux systems, I don’t need a .rootmap file. But on Mac OS X, it turns out that it can help. When I create a .rootmap file, roughly half the programs that used to fail now succeed, whether they were compiled via cmake or I compile them stand-alone:

if ( MACOSX )
   ROOT_GENERATE_DICTIONARY (
      ${CMAKE_CURRENT_BINARY_DIR}/Dictionary
      ${headerList}
      LINKDEF ${LinkDef}
      OPTIONS
        -s ${PROJECT_BINARY_DIR}/Dictionary
        -rmf ${PROJECT_BINARY_DIR}/Dictionary.rootmap
   )
else()
   ROOT_GENERATE_DICTIONARY (
      ${CMAKE_CURRENT_BINARY_DIR}/Dictionary
      ${headerList}
      LINKDEF ${LinkDef}
      OPTIONS
        -s ${PROJECT_BINARY_DIR}/Dictionary
   )
endif()

I’m still trying to understand what the “failing” vs. “succeeding” programs have in common as categories. The code for accessing the dictionary looks identical to me, but I’ll keep comparing.

I think maybe I have the answer. It’s not a satisfactory answer, but it works:

if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
    set(MACOSX TRUE)
endif()

if ( MACOSX )
   ROOT_GENERATE_DICTIONARY (
      ${CMAKE_CURRENT_BINARY_DIR}/Dictionary
      ${headerList}
      LINKDEF ${LinkDef}
      OPTIONS
        -s ${PROJECT_BINARY_DIR}/Dictionary
        -rmf ${PROJECT_BINARY_DIR}/Dictionary.rootmap
        -rml Dictionary
)
else()
   ROOT_GENERATE_DICTIONARY (
      ${CMAKE_CURRENT_BINARY_DIR}/Dictionary
      ${headerList}
      LINKDEF ${LinkDef}
      OPTIONS
        -s ${PROJECT_BINARY_DIR}/Dictionary
   )
endif()

The difference between this and my previous reply is the additional option in ROOT_GENERATE_DICTIONARY:

   -rml Dictionary

Don’t ask me why this works. I figured it out mostly through trial-and-error, with the final clue coming from this cpppy page. Perhaps someone else can explain it. For my part, I now have a software suite that I can compile, load, and execute on Mac OS X. That will have to do for now.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.