Wrong definition between different modules

Hello,

I have an issue using new generation dictionary using cxxmodule. I managed to generate dictionary by rearranging ./include/ directory and relative paths between source/binary and installation directories. However, I just noticed now that when I switched from rootmap to modulemap that and execute a third party program, I get the following error/warning.

Would you have any advice how to fix that ? I am running out of idea.
Just to confirm, here is my project architecture:

  • MyLibA contains a class MyClassA;
  • MyLibB uses MyLibA and includes #include <MyClassA.h> in MyClassB.h
  • MyLibB gets compiled and linked to a third-party program called MyProgram.C
> $ ./MyProgram
Info in <TCanvas::MakeDefCanvas>:  created default TCanvas with name c1
In module 'MyLibA':
/usr/local/libA/../include/MyClassA.h:175:15: error: 'MyClassA' has different definitions in different modules; first difference is definition in module 'MyLibA.../include/MyClassA.h' found method 'DeclFileName' with body
              ClassDef(MyClassA,1);
              ^~~~~~~~~~~~~~~~~~
/usr/local/root/include/Rtypes.h:338:4: note: expanded from macro 'ClassDef'
   _ClassDefOutline_(name,id,virtual,)               \
   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/root/include/Rtypes.h:304:4: note: expanded from macro '_ClassDefOutline_'
   _ClassDefBase_(name,id, virtual_keyword, overrd)                                                             \
   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/root/include/Rtypes.h:301:88: note: expanded from macro '_ClassDefBase_'
   /** \return Name of the file containing the class declaration */ static const char *DeclFileName() { return __FILE__; }
                                                                    ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/libA/../include/MyClassA.h:175:15: note: but in 'MyLibB.../include/MyLibB/MyClassB.h' found method 'DeclFileName' with different body
              ClassDef(MyClassA,1);
              ^~~~~~~~~~~~~~~~~~
/usr/local/root/include/Rtypes.h:338:4: note: expanded from macro 'ClassDef'
   _ClassDefOutline_(name,id,virtual,)               \
   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/root/include/Rtypes.h:304:4: note: expanded from macro '_ClassDefOutline_'
   _ClassDefBase_(name,id, virtual_keyword, overrd)                                                             \
   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/root/include/Rtypes.h:301:88: note: expanded from macro '_ClassDefBase_'
   /** \return Name of the file containing the class declaration */ static const char *DeclFileName() { return __FILE__; }
                                                                    ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

OS: macOS 15.3
ROOT: 6.32.04

Hello @meyerma,

thank you for your question. Let me tag @pcanal for some more insights.

Cheers,
Marta

1 Like

This is very odd. Do you have a reproduce to share?

Dear @pcanal,

I think I found the answer while preparing the reproducerā€¦

This appears to be some conflicts in the #include instructions used and incorrect linking of the include directories.

#include "MyClassB.h"

vs.

#include <MyClassB.h>

Hello @pcanal

OK, Sorry for the confusion, I just managed to narrow my issue down in a more proper way!
The initial issue remains. In short, I have MyClassA.h from MyLibA called in MyClassB.h from MyLibB. (Both are generated using cling cxxmodules)

As I narrowed this issue down, I could isolate as following: I am now just importing MyLibA in a compiled Main.C file.
NB: MyClassA is a templated class. The error appears regardless the content of MyClass::MyFn (MyEnum being a enum class object)

int main(int argc, char **argv)
{
        TApplication *app = new TApplication("myapp", NULL, NULL);
        
        TH1 *h = new TH1D("h", "h", 1, 0, 1);
        TList* functions = h->GetListOfFunctions();

        // Add new TExec
        functions->Add(new TExec(
                TString(h->GetName())+"::MyFn", TString::Format("MyClassA<MyEnum>::MyFn(\"%s\");", h->GetName())
        ));

        h->Draw();
        while(1)
            gSystem->ProcessEvents();

        return 0;
}

The error is the same as my initial message and is raised only at the drawing stage when histogram is drawn and only after pad gets updated. (thatā€™s mainly why I got confused in my previous messageā€¦)

I am now just importing

What do you mean by importing?

Can you provide a complete reproducer?

Dear @pcanal,

Please find the reproducer attached.

Just untar, go inside the directory and run make
It should compile libA, libB and MyProgram will be executed as shown in the log file.

pcanal.tar.gz (1.3 MB)
pcanal.log.txt (9.6 KB)

M.

@pcanal if you have any difficulties using the reproducer, please let me know

After:

make clean
make

I get:

-- Build files have been written to: /home/pcanal/root_working/test/2025-dict/pcanal/MyLibA/var/build
make[2]: Entering directory '/home/pcanal/root_working/test/2025-dict/pcanal/MyLibA/var/build'
make[2]: *** No rule to make target 'install'.  Stop.
make[2]: Leaving directory '/home/pcanal/root_working/test/2025-dict/pcanal/MyLibA/var/build'
make[1]: Leaving directory '/home/pcanal/root_working/test/2025-dict/pcanal'
/bin/sh: line 1: MyLibA/var/install/thislib.*.sh: No such file or directory

Apparently this was due to my default CMake Generator being Ninja

I can reproduce the problem. The symptoms indicates that the pcm file A.pcm and B.pcm where independently created with the 2 ā€˜versionā€™ of the MyClassA.h header file, once from the source directory and once from the install directory. The solution is likely to load the A.pcm during the generation of the dictionary for B (@vvassilev might be able to comment further)

1 Like

Hi @meyerma,

Can you prepare a zip file where I can unzip against ROOT and debug?

Best, Vassil

hi @vvassilev, do you mean like pcanal.tar.gz file, but in zip format ?

Apologies. I overlooked it ā€“ will get back to you.

Ok, I think I understand whatā€™s going on. Iā€™d suggest to simplify a bit by:

  • Replace the #pragma once with proper include protectors
  • Do not make the install step ā€“ that is for now we should only have a single source of truth.

Let me know if you get a different errorā€¦

EDIT: The next step would be to make the modulemap files for your library headers look like ROOT.modulemap. That is, we need to enumerate the headers without any extra paths. That can happen by adjusting the right include paths to the rootcling invocationā€¦

Is #pragma once incorrect protector ??
Would you recommend that below ?

#pragma once
#ifndef _MYHEADER_H_
#define _MYHEADER_H_
...
#endif

Also, I am not sure to understand about the install step. Do you mean for the purpose of this test?
(I basically ship libA through a pre-compile package distribution.)

#pragma once tells the compiler to automatically generate an include protector. Most implementations take the file path to the file. That becomes problematic when the compiler needs to check if two files are the same when they were movedā€¦

[quote=ā€œmeyerma, post:16, topic:63035ā€]
Would you recommend that below ?

#ifndef _MYHEADER_H_
#define _MYHEADER_H_
...
#endif

Right now, it seems to me that we have include paths that somehow point to both the build location and the install location. Moreover, the modulemap files in the install location are emptyā€¦

Generally thatā€™s a good read: root/README/README.CXXMODULES.md at master Ā· root-project/root Ā· GitHub

Thank you @vvassilev ! I will test that and get back to you soon then.

Additional: I am used to play with modulemap file location and paths because the ROOT cmake include is hardcoding modulemap name. Would it be appreciated if I make a PR to make it customizable? I believe cling_modulemap_files variable can be used to add some flexibilities, in particular, if we have multiple libraries (libXXX.modulemap). What do you think?

We have support for custom modulemap names and pcm locations through CLING_MODULEMAP_FILES and CLING_PREBUILT_MODULE_PATH which are path-separator separated env variables. In rootcling we have the relevant flags to control that iircā€¦