Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
TModuleGenerator.cxx
Go to the documentation of this file.
1 // @(#)root/utils:$Id$
2 // Author: Axel Naumann, 2-13-07-02
3 // Author: Danilo Piparo, 2013, 2014
4 
5 /*************************************************************************
6  * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
7  * All rights reserved. *
8  * *
9  * For the licensing terms see $ROOTSYS/LICENSE. *
10  * For the list of contributors see $ROOTSYS/README/CREDITS. *
11  *************************************************************************/
12 
13 ////////////////////////////////////////////////////////////////////////////////
14 //
15 // PCM writer.
16 //
17 ////////////////////////////////////////////////////////////////////////////////
18 
19 #include "TModuleGenerator.h"
20 
21 #include "TClingUtils.h"
22 #include "RConfigure.h"
23 #include <ROOT/RConfig.hxx>
24 
25 #include "cling/Interpreter/CIFactory.h"
26 #include "clang/Basic/SourceManager.h"
27 #include "clang/Frontend/CompilerInstance.h"
28 #include "clang/Lex/HeaderSearch.h"
29 #include "clang/Lex/Preprocessor.h"
30 #include "llvm/Support/Path.h"
31 
32 #include <map>
33 
34 #ifndef R__WIN32
35 #include <unistd.h>
36 #endif
37 
38 #include <iostream>
39 
40 using namespace ROOT;
41 using namespace clang;
42 
43 ////////////////////////////////////////////////////////////////////////////////
44 
45 TModuleGenerator::TModuleGenerator(CompilerInstance *CI,
46  bool inlineInputHeaders,
47  const std::string &shLibFileName,
48  bool writeEmptyRootPCM):
49  fCI(CI),
50  fIsPCH(shLibFileName == "allDict.cxx"),
51  fIsInPCH(writeEmptyRootPCM),
52  fInlineInputHeaders(inlineInputHeaders),
53  fDictionaryName(llvm::sys::path::stem(shLibFileName)),
54  fDemangledDictionaryName(llvm::sys::path::stem(shLibFileName)),
55  fModuleDirName(llvm::sys::path::parent_path(shLibFileName)),
56  fErrorCount(0)
57 {
58  // Need to resolve _where_ to create the pcm
59  // We default in the lib subdirectory
60  // otherwise we put it in the same directory as the dictionary file (for ACLiC)
61  if (fModuleDirName.empty()) {
62  fModuleDirName = "./";
63  } else {
64  fModuleDirName += "/";
65  }
66 
67  fModuleFileName = fModuleDirName
68  + ROOT::TMetaUtils::GetModuleFileName(fDictionaryName.c_str());
69 
70  // Clean the dictionary name from characters which are not accepted in C++
71  std::string tmpName = fDictionaryName;
72  fDictionaryName.clear();
73  ROOT::TMetaUtils::GetCppName(fDictionaryName, tmpName.c_str());
74 
75  // .pcm -> .pch
76  if (IsPCH()) fModuleFileName[fModuleFileName.length() - 1] = 'h';
77 
78  // Add a random string to the filename to avoid races
79  llvm::SmallString<10> resultPath("%%%%%%%%%%");
80  llvm::sys::fs::createUniqueFile(resultPath.str(), resultPath);
81  fUmbrellaName = fModuleDirName + fDictionaryName + resultPath.c_str() + "_dictUmbrella.h";
82  fContentName = fModuleDirName + fDictionaryName + resultPath.c_str() + "_dictContent.h";
83 }
84 
85 TModuleGenerator::~TModuleGenerator()
86 {
87  unlink(fUmbrellaName.c_str());
88  unlink(fContentName.c_str());
89 }
90 
91 ////////////////////////////////////////////////////////////////////////////////
92 /// Check whether the file's extension is compatible with C or C++.
93 /// Return whether source, header, Linkdef or nothing.
94 
95 TModuleGenerator::ESourceFileKind
96 TModuleGenerator::GetSourceFileKind(const char *filename) const
97 {
98  if (filename[0] == '-') return kSFKNotC;
99 
100  if (ROOT::TMetaUtils::IsLinkdefFile(filename)) {
101  return kSFKLinkdef;
102  }
103 
104  const size_t len = strlen(filename);
105  const char *ext = filename + len - 1;
106  while (ext >= filename && *ext != '.') --ext;
107  if (ext < filename || *ext != '.') {
108  // This might still be a system header, let's double check
109  // via the FileManager.
110  clang::Preprocessor &PP = fCI->getPreprocessor();
111  clang::HeaderSearch &HdrSearch = PP.getHeaderSearchInfo();
112  const clang::DirectoryLookup *CurDir = 0;
113  const clang::FileEntry *hdrFileEntry
114  = HdrSearch.LookupFile(filename, clang::SourceLocation(),
115  true /*isAngled*/, 0 /*FromDir*/, CurDir,
116  clang::ArrayRef<std::pair<const clang::FileEntry*,
117  const clang::DirectoryEntry*>>(),
118  0 /*IsMapped*/, 0 /*SearchPath*/, 0 /*RelativePath*/,
119  0 /*RequestingModule*/, 0/*SuggestedModule*/);
120  if (hdrFileEntry) {
121  return kSFKHeader;
122  }
123  return kSFKNotC;
124  }
125  ++ext;
126  const size_t lenExt = filename + len - ext;
127 
128  ESourceFileKind ret = kSFKNotC;
129  switch (lenExt) {
130  case 1: {
131  const char last = toupper(filename[len - 1]);
132  if (last == 'H') ret = kSFKHeader;
133  else if (last == 'C') ret = kSFKSource;
134  break;
135  }
136  case 2: {
137  if (filename[len - 2] == 'h' && filename[len - 1] == 'h')
138  ret = kSFKHeader;
139  else if (filename[len - 2] == 'c' && filename[len - 1] == 'c')
140  ret = kSFKSource;
141  break;
142  }
143  case 3: {
144  const char last = filename[len - 1];
145  if ((last == 'x' || last == 'p')
146  && filename[len - 2] == last) {
147  if (filename[len - 3] == 'h') ret = kSFKHeader;
148  else if (filename[len - 3] == 'c') ret = kSFKSource;
149  }
150  }
151  } // switch extension length
152 
153  return ret;
154 }
155 
156 
157 static
158 std::pair<std::string, std::string> SplitPPDefine(const std::string &in)
159 {
160  std::string::size_type posEq = in.find('=');
161  // No equal found: define to 1
162  if (posEq == std::string::npos)
163  return std::make_pair(in, "1");
164 
165  // Equal found
166  return std::pair<std::string, std::string>
167  (in.substr(0, posEq), in.substr(posEq + 1, std::string::npos));
168 }
169 
170 ////////////////////////////////////////////////////////////////////////////////
171 /// Parse -I -D -U headers.h SomethingLinkdef.h.
172 
173 void TModuleGenerator::ParseArgs(const std::vector<std::string> &args)
174 {
175  for (size_t iPcmArg = 1 /*skip argv0*/, nPcmArg = args.size();
176  iPcmArg < nPcmArg; ++iPcmArg) {
177  ESourceFileKind sfk = GetSourceFileKind(args[iPcmArg].c_str());
178  if (sfk == kSFKHeader || sfk == kSFKSource) {
179  fHeaders.push_back(args[iPcmArg]);
180  } else if (sfk == kSFKLinkdef) {
181  fLinkDefFile = args[iPcmArg];
182  } else if (sfk == kSFKNotC && args[iPcmArg][0] == '-') {
183  switch (args[iPcmArg][1]) {
184  case 'I':
185  if (args[iPcmArg] != "-I." && args[iPcmArg] != "-Iinclude") {
186  fCompI.push_back(args[iPcmArg].c_str() + 2);
187  }
188  break;
189  case 'D':
190  if (args[iPcmArg] != "-DTRUE=1" && args[iPcmArg] != "-DFALSE=0"
191  && args[iPcmArg] != "-DG__NOCINTDLL") {
192  fCompD.push_back(SplitPPDefine(args[iPcmArg].c_str() + 2));
193  }
194  break;
195  case 'U':
196  fCompU.push_back(args[iPcmArg].c_str() + 2);
197  break;
198  }
199  }
200  }
201 }
202 
203 ////////////////////////////////////////////////////////////////////////////////
204 /// Write
205 /// #ifndef FOO
206 /// # define FOO=bar
207 /// #endif
208 
209 std::ostream &TModuleGenerator::WritePPDefines(std::ostream &out) const
210 {
211  for (auto const & strPair : fCompD) {
212  std::string cppname(strPair.first);
213  size_t pos = cppname.find('(');
214  if (pos != std::string::npos) cppname.erase(pos);
215  out << "#ifndef " << cppname << "\n"
216  " #define " << strPair.first;
217  out << " " << strPair.second;
218  out << "\n"
219  "#endif\n";
220  }
221  out << std::endl;
222  return out;
223 }
224 
225 ////////////////////////////////////////////////////////////////////////////////
226 /// Write
227 /// #ifdef FOO
228 /// # undef FOO
229 /// #endif
230 
231 std::ostream &TModuleGenerator::WritePPUndefines(std::ostream &out) const
232 {
233  for (auto const & undef : fCompU) {
234  out << "#ifdef " << undef << "\n"
235  " #undef " << undef << "\n"
236  "#endif\n";
237  }
238  out << std::endl;
239  return out;
240 }
241 
242 ////////////////////////////////////////////////////////////////////////////////
243 /// To be replaced with proper pragma handlers.
244 
245 int WarnIfPragmaOnceDetected(const std::string& fullHeaderPath,
246  const std::string& headerFileContent)
247 {
248  std::istringstream headerFile(headerFileContent);
249  std::string line;
250  while(std::getline(headerFile,line)){
251  llvm::StringRef lineRef (line);
252  auto trimmedLineRef = lineRef.trim();
253  if (trimmedLineRef.startswith("#pragma") &&
254  (trimmedLineRef.endswith(" once") || trimmedLineRef.endswith("\tonce"))) {
255  std::cerr << "Error: #pragma once directive detected in header file "
256  << fullHeaderPath
257  << " which was requested to be inlined.\n";
258  return 1;
259  }
260  }
261  return 0;
262 }
263 
264 ////////////////////////////////////////////////////////////////////////////////
265 
266 int ExtractBufferContent(const std::string& fullHeaderPath, std::string& bufferContent)
267 {
268  std::ifstream buffer(fullHeaderPath);
269  bufferContent = std::string((std::istreambuf_iterator<char>(buffer)),
270  std::istreambuf_iterator<char>());
271 
272  return WarnIfPragmaOnceDetected(fullHeaderPath,bufferContent);
273 }
274 
275 ////////////////////////////////////////////////////////////////////////////////
276 /// Write
277 /// #include "header1.h"
278 /// #include "header2.h"
279 /// or, if inlining of headers is requested, dump the content of the files.
280 
281 std::ostream &TModuleGenerator::WritePPIncludes(std::ostream &out) const
282 {
283  std::string fullHeaderPath;
284  for (auto const & incl : fHeaders) {
285  if (fInlineInputHeaders){
286  bool headerFound = FindHeader(incl,fullHeaderPath);
287  if (!headerFound){
288  ROOT::TMetaUtils::Error(0, "Cannot find header %s: cannot inline it.\n", fullHeaderPath.c_str());
289  continue;
290  }
291 
292  std::string bufferContent;
293  fErrorCount += ExtractBufferContent(fullHeaderPath, bufferContent);
294 
295  out << bufferContent << std::endl;
296  } else {
297  out << "#include \"" << incl << "\"\n";
298  }
299  }
300  out << std::endl;
301  return out;
302 }
303 
304 ////////////////////////////////////////////////////////////////////////////////
305 
306 std::ostream &TModuleGenerator::WriteStringVec(const std::vector<std::string> &vec,
307  std::ostream &out) const
308 {
309  for (auto const & theStr : vec) {
310  out << "\"" << theStr << "\",\n";
311  }
312  out << "0" << std::endl;
313  return out;
314 }
315 
316 ////////////////////////////////////////////////////////////////////////////////
317 
318 std::ostream &TModuleGenerator::WriteStringPairVec(const StringPairVec_t &vec,
319  std::ostream &out) const
320 {
321  for (auto const & strPair : vec) {
322  out << "\"" << strPair.first;
323  if (!strPair.second.empty()) {
324  out << "=";
325  // Need to escape the embedded quotes.
326  for (const char *c = strPair.second.c_str(); *c != '\0'; ++c) {
327  if (*c == '"') {
328  out << "\\\"";
329  } else {
330  out << *c;
331  }
332  }
333  }
334  out << "\",\n";
335  }
336  out << "0" << std::endl;
337  return out;
338 }
339 
340 
341 void TModuleGenerator::WriteRegistrationSourceImpl(std::ostream& out,
342  const std::string &dictName,
343  const std::string &demangledDictName,
344  const std::vector<std::string> &headerArray,
345  const std::vector<std::string> &includePathArray,
346  const std::string &fwdDeclStringRAW,
347  const std::string &fwdDeclnArgsToKeepString,
348  const std::string &payloadCodeWrapped,
349  const std::string &headersClassesMapString,
350  const std::string &extraIncludes,
351  bool hasCxxModule) const
352 {
353  // Dictionary initialization code for loading the module
354  out << "namespace {\n"
355  " void TriggerDictionaryInitialization_" << dictName << "_Impl() {\n"
356  " static const char* headers[] = {\n";
357  WriteStringVec(headerArray, out) << " };\n";
358  out << " static const char* includePaths[] = {\n";
359  WriteStringVec(includePathArray, out)
360  << " };\n";
361 
362  out << " static const char* fwdDeclCode = " << fwdDeclStringRAW << ";\n"
363  << " static const char* payloadCode = " << payloadCodeWrapped << ";\n";
364  // classesHeaders may depen on payloadCode
365  out << " static const char* classesHeaders[] = {\n"
366  << headersClassesMapString
367  << "\n};\n";
368  out << " static bool isInitialized = false;\n"
369  " if (!isInitialized) {\n"
370  " TROOT::RegisterModule(\"" << demangledDictName << "\",\n"
371  " headers, includePaths, payloadCode, fwdDeclCode,\n"
372  " TriggerDictionaryInitialization_" << dictName << "_Impl, "
373  << fwdDeclnArgsToKeepString << ", classesHeaders, "
374  << (hasCxxModule ? "/*hasCxxModule*/true" : "/*hasCxxModule*/false")
375  << ");\n"
376  " isInitialized = true;\n"
377  " }\n"
378  " }\n"
379  " static struct DictInit {\n"
380  " DictInit() {\n"
381  " TriggerDictionaryInitialization_" << dictName << "_Impl();\n"
382  " }\n"
383  " } __TheDictionaryInitializer;\n"
384  "}\n"
385  "void TriggerDictionaryInitialization_" << dictName << "() {\n"
386  " TriggerDictionaryInitialization_" << dictName << "_Impl();\n"
387  "}\n";
388 }
389 
390 ////////////////////////////////////////////////////////////////////////////////
391 
392 void TModuleGenerator::WriteRegistrationSource(std::ostream &out, const std::string &fwdDeclnArgsToKeepString,
393  const std::string &headersClassesMapString,
394  const std::string &fwdDeclString, const std::string &extraIncludes, bool hasCxxModule) const
395 {
396  if (hasCxxModule) {
397  std::string emptyStr = "\"\"";
398  WriteRegistrationSourceImpl(out, GetDictionaryName(), GetDemangledDictionaryName(), {}, {},
399  fwdDeclString, "{}",
400  emptyStr, headersClassesMapString, "0",
401  /*HasCxxModule*/ true);
402  return;
403  }
404 
405  std::string fwdDeclStringSanitized = fwdDeclString;
406 #ifdef R__WIN32
407  // Visual sudio has a limitation of 2048 characters max in raw strings, so split
408  // the potentially huge DICTFWDDCLS raw string into multiple smaller ones
409  constexpr char from[] = "\n";
410  constexpr char to[] = "\n)DICTFWDDCLS\"\nR\"DICTFWDDCLS(";
411  size_t start_pos = 0;
412  while ((start_pos = fwdDeclStringSanitized.find(from, start_pos)) != std::string::npos) {
413  if (fwdDeclStringSanitized.find(from, start_pos + 1) == std::string::npos) // skip the last
414  break;
415  if ((fwdDeclStringSanitized.at(start_pos + 1) == '}') || (fwdDeclStringSanitized.at(start_pos + 1) == '\n'))
416  start_pos += 2;
417  else {
418  fwdDeclStringSanitized.replace(start_pos, strlen(from), to);
419  start_pos += strlen(to); // In case 'to' contains 'from', like replacing 'x' with 'yx'
420  }
421  }
422 #endif
423  std::string fwdDeclStringRAW;
424  if ("nullptr" == fwdDeclStringSanitized || "\"\"" == fwdDeclStringSanitized) {
425  fwdDeclStringRAW = fwdDeclStringSanitized;
426  } else {
427  fwdDeclStringRAW = "R\"DICTFWDDCLS(\n";
428  fwdDeclStringRAW += "#line 1 \"";
429  fwdDeclStringRAW += fDictionaryName +" dictionary forward declarations' payload\"\n";
430  fwdDeclStringRAW += fwdDeclStringSanitized;
431  fwdDeclStringRAW += ")DICTFWDDCLS\"";
432  }
433 
434  if (fIsInPCH)
435  fwdDeclStringRAW = "nullptr";
436 
437  std::string payloadCode;
438 
439  // Increase the value of the diagnostics pointing out from which
440  // dictionary this payload comes from in case of errors
441  payloadCode += "#line 1 \"";
442  payloadCode += fDictionaryName +" dictionary payload\"\n";
443 
444  // Add defines and undefines to the payloadCode
445  std::ostringstream definesAndUndefines;
446  // Anticipate the undefines.
447  // Suppose to have a namespace called "declarations" used in R5 for template
448  // instantiations in the header given to genreflex.
449  // Now, in this namespace, objects with some names, typically dummy, will be
450  // present.
451  // If you give such headers to cling to parse, problems will occour, as the
452  // names appear multiple times. One possible solution is to get out of this
453  // with preprocessor defines given to genreflex, redefining "declarations"
454  // to a hash or <project>_<package> via the build system.
455  WritePPUndefines(definesAndUndefines);
456  WritePPDefines(definesAndUndefines);
457  payloadCode += definesAndUndefines.str();
458 
459  // If necessary, inline the headers
460  std::string hdrFullPath;
461  std::string inlinedHeaders;
462 
463  auto findAndAddToInlineHeaders = [&](const std::string& hdrName) {
464  bool headerFound = FindHeader(hdrName,hdrFullPath);
465  if (!headerFound) {
466  ROOT::TMetaUtils::Error(0, "Cannot find header %s: cannot inline it.\n", hdrName.c_str());
467  } else {
468  std::ifstream headerFile(hdrFullPath.c_str());
469  const std::string headerFileAsStr((std::istreambuf_iterator<char>(headerFile)),
470  std::istreambuf_iterator<char>());
471  inlinedHeaders += headerFileAsStr;
472  }
473  };
474 
475  if (fInlineInputHeaders) {
476  for (auto const & hdrName : fHeaders) {
477  findAndAddToInlineHeaders(hdrName);
478  }
479 
480  } else {
481  // Now, if not, just #include them in the payload except for the linkdef
482  // file, if any.
483  for (auto & hdrName : fHeaders) {
484  inlinedHeaders += "#include \"" + hdrName + "\"\n";
485  }
486  }
487 
488  if (0 != fLinkDefFile.size() && !fIsPCH) {
489  findAndAddToInlineHeaders(fLinkDefFile);
490  }
491 
492  // Recover old genreflex behaviour, i.e. do not print warnings due to glitches
493  // in the headers at runtime. This is not synonym of ignoring warnings as they
494  // will be printed at dictionary generation time.
495  // In order to do this we leverage the diagnostic pragmas and, since there is no
496  // way to express as a pragma the option "-Wno-deprecated" the
497  // _BACKWARD_BACKWARD_WARNING_H macro, used to avoid to go through
498  // backward/backward_warning.h.
499  payloadCode += "#define _BACKWARD_BACKWARD_WARNING_H\n"
500  "// Inline headers\n"+
501  inlinedHeaders + "\n"+
502  (extraIncludes.empty() ? "" : "// Extra includes\n" + extraIncludes + "\n") +
503  "#undef _BACKWARD_BACKWARD_WARNING_H\n";
504 
505  // We cannot stream the contents in strings and pass it to
506  // WriteRegistrationSourceImpl because we exceed the std::string::max_size on
507  // Windows.
508  std::vector<std::string> headerArray = {"0"};
509  if (!fInlineInputHeaders)
510  headerArray = fHeaders;
511  const std::vector<std::string>& includePathArray = fCompI;
512 
513  std::string payloadcodeWrapped = "nullptr";
514  if (!fIsInPCH)
515  payloadcodeWrapped = "R\"DICTPAYLOAD(\n" + payloadCode + ")DICTPAYLOAD\"";
516 
517  WriteRegistrationSourceImpl(out, GetDictionaryName(),
518  GetDemangledDictionaryName(),
519  headerArray,
520  includePathArray,
521  fwdDeclStringRAW,
522  fwdDeclnArgsToKeepString,
523  payloadcodeWrapped,
524  headersClassesMapString,
525  extraIncludes,
526  /*HasCxxModule*/false);
527 }
528 
529 ////////////////////////////////////////////////////////////////////////////////
530 /// Write a header file describing the content of this module
531 /// through a series of variables inside the namespace
532 /// ROOT::Dict::[DictionaryName]. Each variable is an array of string
533 /// literals, with a const char* of 0 being the last element, e.g.
534 /// ROOT::Dict::_DictName::arrIncludes[] = { "A.h", "B.h", 0 };
535 
536 void TModuleGenerator::WriteContentHeader(std::ostream &out) const
537 {
538  out << "namespace ROOT { namespace Dict { namespace _"
539  << GetDictionaryName() << "{\n";
540 
541  out << "const char* arrIncludes[] = {\n";
542  WriteHeaderArray(out) << "};\n";
543 
544  out << "const char* arrIncludePaths[] = {\n";
545  WriteIncludePathArray(out) << "};\n";
546  /*
547  out << "const char* arrDefines[] = {\n";
548  WriteDefinesArray(out) << "};\n";
549 
550  out << "const char* arrUndefines[] = {\n";
551  WriteUndefinesArray(out) << "};\n";*/
552 
553  out << "} } }" << std::endl;
554 }
555 
556 ////////////////////////////////////////////////////////////////////////////////
557 /// Return true if the header is found in the include paths
558 /// in this case also fill the full path variable with the full path.
559 
560 bool TModuleGenerator::FindHeader(const std::string &hdrName, std::string &hdrFullPath) const
561 {
562  hdrFullPath = hdrName;
563  if (llvm::sys::fs::exists(hdrFullPath))
564  return true;
565  for (auto const &incDir : fCompI) {
566  hdrFullPath = incDir + ROOT::TMetaUtils::GetPathSeparator() + hdrName;
567  if (llvm::sys::fs::exists(hdrFullPath)) {
568  return true;
569  }
570  }
571  clang::Preprocessor &PP = fCI->getPreprocessor();
572  clang::HeaderSearch &HdrSearch = PP.getHeaderSearchInfo();
573  const clang::DirectoryLookup *CurDir = 0;
574  if (const clang::FileEntry *hdrFileEntry
575  = HdrSearch.LookupFile(hdrName, clang::SourceLocation(),
576  true /*isAngled*/, 0 /*FromDir*/, CurDir,
577  clang::ArrayRef<std::pair<const clang::FileEntry*,
578  const clang::DirectoryEntry*>>(),
579  0 /*IsMapped*/, 0 /*SearchPath*/, 0 /*RelativePath*/,
580  0 /*RequestingModule*/, 0/*SuggestedModule*/)) {
581  hdrFullPath = hdrFileEntry->getName();
582  return true;
583  }
584 
585  return false;
586 }
587 
588 ////////////////////////////////////////////////////////////////////////////////
589 /// Write a header file pulling in the content of this module
590 /// through a series of #defined, #undefs and #includes.
591 /// The sequence corrsponds to a rootcling invocation with
592 /// -c -DFOO -UBAR header.h
593 /// I.e. defines, undefines and finally includes.
594 
595 void TModuleGenerator::WriteUmbrellaHeader(std::ostream &out) const
596 {
597  WritePPDefines(out);
598  WritePPUndefines(out);
599  WritePPIncludes(out);
600 }