Skip to content
Open
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
16 changes: 11 additions & 5 deletions cmake/modules/RootMacros.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -685,10 +685,10 @@ function(ROOT_GENERATE_DICTIONARY dictionary)
endforeach()

#---build the implicit dependencies arguments
# NOTE: only the Makefile generator respects this!
foreach(_dep ${_linkdef} ${_list_of_header_dependencies})
list(APPEND _implicitdeps CXX ${_dep})
endforeach()
# NOTE: DEPFILE is used instead of IMPLICIT_DEPENDS because IMPLICIT_DEPENDS
# only works with Unix Makefiles and has issues with cross-directory dependencies.
# DEPFILE works with all generators (Ninja, Unix Makefiles, etc.)
set(depfile_path ${CMAKE_CURRENT_BINARY_DIR}/${dictionary}.depfile)

if(ARG_MODULE)
set(MODULE_LIB_DEPENDENCY ${ARG_DEPENDENCIES})
Expand Down Expand Up @@ -728,6 +728,10 @@ function(ROOT_GENERATE_DICTIONARY dictionary)
endif()

#---call rootcling------------------------------------------
# use CMP0116 NEW locally so CMake normalises the depfile
# target for the active generator regardless of the global OLD setting.
cmake_policy(PUSH)
cmake_policy(SET CMP0116 NEW)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems that the current LLVM code can handle (when it needs to) CMP0116 to be NEW (See content of interpreter/llvm-project/llvm/cmake/modules/TableGen.cmake ; and the fact that the code refered to in 7392b02 in no longer in interpreter/llvm-project/cmake/Modules/CMakePolicy.cmake).

I would strongly recommend that we also (in a separate PR) set CMP0116 to be NEW globally.

add_custom_command(
OUTPUT ${dictionary}.cxx ${pcm_name} ${rootmap_name} ${cpp_module_file}
COMMAND ${command} -v2 -f ${dictionary}.cxx ${newargs} ${excludepathsargs} ${rootmapargs}
Expand All @@ -741,14 +745,16 @@ function(ROOT_GENERATE_DICTIONARY dictionary)
# make the dictionary generation command depend on the C++ standard, ensuring that the
# dictionaries will be rebuilt if the C++ standard is changed in an incremental build.
-DR__DUMMY_CXX_STANDARD_${CMAKE_CXX_STANDARD}
IMPLICIT_DEPENDS ${_implicitdeps}
-MF ${depfile_path}
DEPFILE ${depfile_path}
DEPENDS ${_list_of_header_dependencies} ${_linkdef} ${ROOTCLINGDEP}
${pcm_dependencies}
${MODULE_LIB_DEPENDENCY} ${ARG_EXTRA_DEPENDENCIES}
${runtime_cxxmodule_dependencies}
${cxx_std_stamp}
COMMAND_EXPAND_LISTS
)
cmake_policy(POP)

# If we are adding to an existing target and it's not the dictionary itself,
# we make an object library and add its output object file as source to the target.
Expand Down
96 changes: 96 additions & 0 deletions core/dictgen/src/rootcling_impl.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -3709,6 +3709,10 @@ static llvm::cl::list<std::string>
gOptWDiags("W", llvm::cl::Prefix, llvm::cl::ZeroOrMore,
llvm::cl::desc("Specify compiler diagnostics options."),
llvm::cl::cat(gRootclingOptions));
static llvm::cl::opt<std::string>
gOptDepFile("MF",
llvm::cl::desc("Write dependency output to the specified file."),
llvm::cl::cat(gRootclingOptions));
// Really OneOrMore, will be changed in RootClingMain below.
static llvm::cl::list<std::string>
gOptDictionaryHeaderFiles(llvm::cl::Positional, llvm::cl::ZeroOrMore,
Expand Down Expand Up @@ -4517,6 +4521,11 @@ int RootClingMain(int argc,
// Check if code goes to stdout or rootcling file
std::ofstream fileout;
string main_dictname(gOptDictionaryFileName.getValue());
// Keep the original dictionary output file name (with extension) for the
// dependency file target: `main_dictname` gets its extension stripped below
// and `gOptDictionaryFileName` is turned into a temporary name by the
// tmpCatalog a few lines down.
const std::string dictOutputFileName(gOptDictionaryFileName.getValue());
std::ostream *splitDictStream = nullptr;
std::unique_ptr<std::ostream> splitDeleter(nullptr);
// Store the temp files
Expand Down Expand Up @@ -4999,6 +5008,93 @@ int RootClingMain(int argc,
// make sure the file is closed before committing
fileout.close();

// Write the dependency file if requested (-MF <file>). It uses the
// Makefile format understood by CMake's DEPFILE and Ninja's "deps = gcc",
// listing every real header that was opened while generating the dictionary
// so that incremental builds pick up changes to transitively included files.
if (!gOptDepFile.empty() && rootclingRetCode == 0 && !dictOutputFileName.empty()) {
std::ofstream depFile(gOptDepFile.c_str());
if (!depFile) {
ROOT::TMetaUtils::Error(nullptr,
"rootcling: failed to open dependency file %s\n",
gOptDepFile.c_str());
rootclingRetCode = 1;
} else {
// Escape a path for the Makefile-format dependency file: forward
// slashes (needed on Windows) and backslash-escape the characters that
// are special to make (space, tab, '#', ':').
auto escapeForDepFile = [](std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
std::string escaped;
escaped.reserve(path.size());
for (char c : path) {
if (c == ' ' || c == '\t' || c == '#' || c == ':')
escaped += '\\';
escaped += c;
}
return escaped;
};

// The target is the final dictionary source file. Note that
// gOptDictionaryFileName has been turned into a temporary name by the
// tmpCatalog, so we use the original name captured earlier.
depFile << escapeForDepFile(dictOutputFileName) << ":";

// Collect all files that were read by clang during dictionary
// generation (headers included directly or indirectly).
clang::SourceManager &SM = CI->getSourceManager();
clang::FileManager &FM = SM.getFileManager();

llvm::SmallVector<clang::OptionalFileEntryRef, 64> files;
FM.GetUniqueIDMapping(files);

std::set<std::string> includedFiles;
for (const auto &FEOpt : files) {
if (!FEOpt)
continue;
llvm::StringRef filename = FEOpt->getName();
if (filename.empty())
continue;
// Skip cling's in-memory buffers, which the FileManager also
// reports: "input_line_N", "<<< cling interactive line includer >>>",
// "<built-in>", "<command line>", ... These are not real files;
// some contain spaces or angle brackets that would corrupt the
// dependency file, and all of them would make the dictionary appear
// perpetually out of date. Requiring the entry to exist on disk
// filters them out (together with the explicit angle-bracket check).
if (filename.contains('<') || filename.contains('>'))
continue;
// Make the path absolute so it is unambiguous regardless of the
// working directory: rootcling may run from a different directory
// than the one the dependency file is later consumed from (with
// CMP0116 OLD the depfile is not rewritten, and a relative entry
// like "./Foo.hxx" would be resolved against the wrong base and
// leave the dictionary permanently out of date).
llvm::SmallString<256> absPath(filename);
llvm::sys::fs::make_absolute(absPath);
if (!llvm::sys::fs::exists(absPath))
continue;
std::string filenameStr(absPath.str());
// Skip the output dictionary file itself (final or temporary name).
if (filenameStr == dictOutputFileName || filenameStr == gOptDictionaryFileName.getValue())

@pcanal pcanal Aug 5, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are dictOutputFileName and gOptDictionaryFileName guaranteed to be absolute path name?

continue;
includedFiles.insert(std::move(filenameStr));
}

// Each dependency line except the last ends with a backslash.
for (const auto &file : includedFiles)
depFile << " \\\n " << escapeForDepFile(file);
if (!includedFiles.empty())
depFile << "\n";

depFile.close();
if (!depFile.good()) {
ROOT::TMetaUtils::Error(nullptr, "rootcling: failed to write dependency file %s\n", gOptDepFile.c_str());
rootclingRetCode = 1;
}
}
}

// Before returning, rename the files if no errors occurred
// otherwise clean them to avoid remnants (see ROOT-10015)
if(rootclingRetCode == 0) {
Expand Down
Loading