resolve cyclic dependency with zstd
[platform/upstream/cmake.git] / Source / cmOutputRequiredFilesCommand.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "cmOutputRequiredFilesCommand.h"
4
5 #include <cstdio>
6 #include <map>
7 #include <set>
8 #include <utility>
9
10 #include <cm/memory>
11
12 #include "cmsys/FStream.hxx"
13 #include "cmsys/RegularExpression.hxx"
14
15 #include "cmExecutionStatus.h"
16 #include "cmGeneratorExpression.h"
17 #include "cmMakefile.h"
18 #include "cmSourceFile.h"
19 #include "cmStringAlgorithms.h"
20 #include "cmSystemTools.h"
21 #include "cmTarget.h"
22 #include "cmValue.h"
23
24 namespace {
25 /** \class cmDependInformation
26  * \brief Store dependency information for a single source file.
27  *
28  * This structure stores the depend information for a single source file.
29  */
30 class cmDependInformation
31 {
32 public:
33   /**
34    * Construct with dependency generation marked not done; instance
35    * not placed in cmMakefile's list.
36    */
37   cmDependInformation() = default;
38
39   /**
40    * The set of files on which this one depends.
41    */
42   using DependencySetType = std::set<cmDependInformation*>;
43   DependencySetType DependencySet;
44
45   /**
46    * This flag indicates whether dependency checking has been
47    * performed for this file.
48    */
49   bool DependDone = false;
50
51   /**
52    * If this object corresponds to a cmSourceFile instance, this points
53    * to it.
54    */
55   const cmSourceFile* SourceFile = nullptr;
56
57   /**
58    * Full path to this file.
59    */
60   std::string FullPath;
61
62   /**
63    * Full path not including file name.
64    */
65   std::string PathOnly;
66
67   /**
68    * Name used to #include this file.
69    */
70   std::string IncludeName;
71
72   /**
73    * This method adds the dependencies of another file to this one.
74    */
75   void AddDependencies(cmDependInformation* info)
76   {
77     if (this != info) {
78       this->DependencySet.insert(info);
79     }
80   }
81 };
82
83 class cmLBDepend
84 {
85 public:
86   /**
87    * Construct the object with verbose turned off.
88    */
89   cmLBDepend()
90   {
91     this->Verbose = false;
92     this->IncludeFileRegularExpression.compile("^.*$");
93     this->ComplainFileRegularExpression.compile("^$");
94   }
95
96   /**
97    * Destructor.
98    */
99   ~cmLBDepend() = default;
100
101   cmLBDepend(const cmLBDepend&) = delete;
102   cmLBDepend& operator=(const cmLBDepend&) = delete;
103
104   /**
105    * Set the makefile that is used as a source of classes.
106    */
107   void SetMakefile(cmMakefile* makefile)
108   {
109     this->Makefile = makefile;
110
111     // Now extract the include file regular expression from the makefile.
112     this->IncludeFileRegularExpression.compile(
113       this->Makefile->GetIncludeRegularExpression());
114     this->ComplainFileRegularExpression.compile(
115       this->Makefile->GetComplainRegularExpression());
116
117     // Now extract any include paths from the targets
118     std::set<std::string> uniqueIncludes;
119     std::vector<std::string> orderedAndUniqueIncludes;
120     for (auto const& target : this->Makefile->GetTargets()) {
121       cmValue incDirProp = target.second.GetProperty("INCLUDE_DIRECTORIES");
122       if (!incDirProp) {
123         continue;
124       }
125
126       std::string incDirs = cmGeneratorExpression::Preprocess(
127         *incDirProp, cmGeneratorExpression::StripAllGeneratorExpressions);
128
129       std::vector<std::string> includes = cmExpandedList(incDirs);
130
131       for (std::string& path : includes) {
132         this->Makefile->ExpandVariablesInString(path);
133         if (uniqueIncludes.insert(path).second) {
134           orderedAndUniqueIncludes.push_back(path);
135         }
136       }
137     }
138
139     for (std::string const& inc : orderedAndUniqueIncludes) {
140       this->AddSearchPath(inc);
141     }
142   }
143
144   /**
145    * Add a directory to the search path for include files.
146    */
147   void AddSearchPath(const std::string& path)
148   {
149     this->IncludeDirectories.push_back(path);
150   }
151
152   /**
153    * Generate dependencies for the file given.  Returns a pointer to
154    * the cmDependInformation object for the file.
155    */
156   const cmDependInformation* FindDependencies(const std::string& file)
157   {
158     cmDependInformation* info = this->GetDependInformation(file, "");
159     this->GenerateDependInformation(info);
160     return info;
161   }
162
163 protected:
164   /**
165    * Compute the depend information for this class.
166    */
167
168   void DependWalk(cmDependInformation* info)
169   {
170     cmsys::ifstream fin(info->FullPath.c_str());
171     if (!fin) {
172       cmSystemTools::Error("error can not open " + info->FullPath);
173       return;
174     }
175
176     std::string line;
177     while (cmSystemTools::GetLineFromStream(fin, line)) {
178       if (cmHasLiteralPrefix(line, "#include")) {
179         // if it is an include line then create a string class
180         size_t qstart = line.find('\"', 8);
181         size_t qend;
182         // if a quote is not found look for a <
183         if (qstart == std::string::npos) {
184           qstart = line.find('<', 8);
185           // if a < is not found then move on
186           if (qstart == std::string::npos) {
187             cmSystemTools::Error("unknown include directive " + line);
188             continue;
189           }
190           qend = line.find('>', qstart + 1);
191         } else {
192           qend = line.find('\"', qstart + 1);
193         }
194         // extract the file being included
195         std::string includeFile = line.substr(qstart + 1, qend - qstart - 1);
196         // see if the include matches the regular expression
197         if (!this->IncludeFileRegularExpression.find(includeFile)) {
198           if (this->Verbose) {
199             std::string message =
200               cmStrCat("Skipping ", includeFile, " for file ", info->FullPath);
201             cmSystemTools::Error(message);
202           }
203           continue;
204         }
205
206         // Add this file and all its dependencies.
207         this->AddDependency(info, includeFile);
208         /// add the cxx file if it exists
209         std::string cxxFile = includeFile;
210         std::string::size_type pos = cxxFile.rfind('.');
211         if (pos != std::string::npos) {
212           std::string root = cxxFile.substr(0, pos);
213           cxxFile = root + ".cxx";
214           bool found = false;
215           // try jumping to .cxx .cpp and .c in order
216           if (cmSystemTools::FileExists(cxxFile)) {
217             found = true;
218           }
219           for (std::string const& path : this->IncludeDirectories) {
220             if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
221               found = true;
222             }
223           }
224           if (!found) {
225             cxxFile = root + ".cpp";
226             if (cmSystemTools::FileExists(cxxFile)) {
227               found = true;
228             }
229             for (std::string const& path : this->IncludeDirectories) {
230               if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
231                 found = true;
232               }
233             }
234           }
235           if (!found) {
236             cxxFile = root + ".c";
237             if (cmSystemTools::FileExists(cxxFile)) {
238               found = true;
239             }
240             for (std::string const& path : this->IncludeDirectories) {
241               if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
242                 found = true;
243               }
244             }
245           }
246           if (!found) {
247             cxxFile = root + ".txx";
248             if (cmSystemTools::FileExists(cxxFile)) {
249               found = true;
250             }
251             for (std::string const& path : this->IncludeDirectories) {
252               if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
253                 found = true;
254               }
255             }
256           }
257           if (found) {
258             this->AddDependency(info, cxxFile);
259           }
260         }
261       }
262     }
263   }
264
265   /**
266    * Add a dependency.  Possibly walk it for more dependencies.
267    */
268   void AddDependency(cmDependInformation* info, const std::string& file)
269   {
270     cmDependInformation* dependInfo =
271       this->GetDependInformation(file, info->PathOnly);
272     this->GenerateDependInformation(dependInfo);
273     info->AddDependencies(dependInfo);
274   }
275
276   /**
277    * Fill in the given object with dependency information.  If the
278    * information is already complete, nothing is done.
279    */
280   void GenerateDependInformation(cmDependInformation* info)
281   {
282     // If dependencies are already done, stop now.
283     if (info->DependDone) {
284       return;
285     }
286     // Make sure we don't visit the same file more than once.
287     info->DependDone = true;
288
289     const std::string& path = info->FullPath;
290     if (path.empty()) {
291       cmSystemTools::Error(
292         "Attempt to find dependencies for file without path!");
293       return;
294     }
295
296     bool found = false;
297
298     // If the file exists, use it to find dependency information.
299     if (cmSystemTools::FileExists(path, true)) {
300       // Use the real file to find its dependencies.
301       this->DependWalk(info);
302       found = true;
303     }
304
305     // See if the cmSourceFile for it has any files specified as
306     // dependency hints.
307     if (info->SourceFile != nullptr) {
308
309       // Get the cmSourceFile corresponding to this.
310       const cmSourceFile& cFile = *(info->SourceFile);
311       // See if there are any hints for finding dependencies for the missing
312       // file.
313       if (!cFile.GetDepends().empty()) {
314         // Dependency hints have been given.  Use them to begin the
315         // recursion.
316         for (std::string const& file : cFile.GetDepends()) {
317           this->AddDependency(info, file);
318         }
319
320         // Found dependency information.  We are done.
321         found = true;
322       }
323     }
324
325     if (!found) {
326       // Try to find the file amongst the sources
327       cmSourceFile* srcFile = this->Makefile->GetSource(
328         cmSystemTools::GetFilenameWithoutExtension(path));
329       if (srcFile) {
330         if (srcFile->ResolveFullPath() == path) {
331           found = true;
332         } else {
333           // try to guess which include path to use
334           for (std::string incpath : this->IncludeDirectories) {
335             if (!incpath.empty() && incpath.back() != '/') {
336               incpath += "/";
337             }
338             incpath += path;
339             if (srcFile->ResolveFullPath() == incpath) {
340               // set the path to the guessed path
341               info->FullPath = incpath;
342               found = true;
343             }
344           }
345         }
346       }
347     }
348
349     if (!found) {
350       // Couldn't find any dependency information.
351       if (this->ComplainFileRegularExpression.find(info->IncludeName)) {
352         cmSystemTools::Error("error cannot find dependencies for " + path);
353       } else {
354         // Destroy the name of the file so that it won't be output as a
355         // dependency.
356         info->FullPath.clear();
357       }
358     }
359   }
360
361   /**
362    * Get an instance of cmDependInformation corresponding to the given file
363    * name.
364    */
365   cmDependInformation* GetDependInformation(const std::string& file,
366                                             const std::string& extraPath)
367   {
368     // Get the full path for the file so that lookup is unambiguous.
369     std::string fullPath = this->FullPath(file, extraPath);
370
371     // Try to find the file's instance of cmDependInformation.
372     auto result = this->DependInformationMap.find(fullPath);
373     if (result != this->DependInformationMap.end()) {
374       // Found an instance, return it.
375       return result->second.get();
376     }
377     // Didn't find an instance.  Create a new one and save it.
378     auto info = cm::make_unique<cmDependInformation>();
379     auto* ptr = info.get();
380     info->FullPath = fullPath;
381     info->PathOnly = cmSystemTools::GetFilenamePath(fullPath);
382     info->IncludeName = file;
383     this->DependInformationMap[fullPath] = std::move(info);
384     return ptr;
385   }
386
387   /**
388    * Find the full path name for the given file name.
389    * This uses the include directories.
390    * TODO: Cache path conversions to reduce FileExists calls.
391    */
392   std::string FullPath(const std::string& fname, const std::string& extraPath)
393   {
394     auto m = this->DirectoryToFileToPathMap.find(extraPath);
395
396     if (m != this->DirectoryToFileToPathMap.end()) {
397       FileToPathMapType& map = m->second;
398       auto p = map.find(fname);
399       if (p != map.end()) {
400         return p->second;
401       }
402     }
403
404     if (cmSystemTools::FileExists(fname, true)) {
405       std::string fp = cmSystemTools::CollapseFullPath(fname);
406       this->DirectoryToFileToPathMap[extraPath][fname] = fp;
407       return fp;
408     }
409
410     for (std::string path : this->IncludeDirectories) {
411       if (!path.empty() && path.back() != '/') {
412         path += "/";
413       }
414       path += fname;
415       if (cmSystemTools::FileExists(path, true) &&
416           !cmSystemTools::FileIsDirectory(path)) {
417         std::string fp = cmSystemTools::CollapseFullPath(path);
418         this->DirectoryToFileToPathMap[extraPath][fname] = fp;
419         return fp;
420       }
421     }
422
423     if (!extraPath.empty()) {
424       std::string path = extraPath;
425       if (!path.empty() && path.back() != '/') {
426         path = path + "/";
427       }
428       path = path + fname;
429       if (cmSystemTools::FileExists(path, true) &&
430           !cmSystemTools::FileIsDirectory(path)) {
431         std::string fp = cmSystemTools::CollapseFullPath(path);
432         this->DirectoryToFileToPathMap[extraPath][fname] = fp;
433         return fp;
434       }
435     }
436
437     // Couldn't find the file.
438     return fname;
439   }
440
441   cmMakefile* Makefile;
442   bool Verbose;
443   cmsys::RegularExpression IncludeFileRegularExpression;
444   cmsys::RegularExpression ComplainFileRegularExpression;
445   std::vector<std::string> IncludeDirectories;
446   using FileToPathMapType = std::map<std::string, std::string>;
447   using DirectoryToFileToPathMapType =
448     std::map<std::string, FileToPathMapType>;
449   using DependInformationMapType =
450     std::map<std::string, std::unique_ptr<cmDependInformation>>;
451   DependInformationMapType DependInformationMap;
452   DirectoryToFileToPathMapType DirectoryToFileToPathMap;
453 };
454
455 void ListDependencies(cmDependInformation const* info, FILE* fout,
456                       std::set<cmDependInformation const*>* visited);
457 }
458
459 // cmOutputRequiredFilesCommand
460 bool cmOutputRequiredFilesCommand(std::vector<std::string> const& args,
461                                   cmExecutionStatus& status)
462 {
463   if (args.size() != 2) {
464     status.SetError("called with incorrect number of arguments");
465     return false;
466   }
467
468   // store the arg for final pass
469   const std::string& file = args[0];
470   const std::string& outputFile = args[1];
471
472   // compute the list of files
473   cmLBDepend md;
474   md.SetMakefile(&status.GetMakefile());
475   md.AddSearchPath(status.GetMakefile().GetCurrentSourceDirectory());
476   // find the depends for a file
477   const cmDependInformation* info = md.FindDependencies(file);
478   if (info) {
479     // write them out
480     FILE* fout = cmsys::SystemTools::Fopen(outputFile, "w");
481     if (!fout) {
482       status.SetError(cmStrCat("Can not open output file: ", outputFile));
483       return false;
484     }
485     std::set<cmDependInformation const*> visited;
486     ListDependencies(info, fout, &visited);
487     fclose(fout);
488   }
489
490   return true;
491 }
492
493 namespace {
494 void ListDependencies(cmDependInformation const* info, FILE* fout,
495                       std::set<cmDependInformation const*>* visited)
496 {
497   // add info to the visited set
498   visited->insert(info);
499   // now recurse with info's dependencies
500   for (cmDependInformation* d : info->DependencySet) {
501     if (visited->find(d) == visited->end()) {
502       if (!info->FullPath.empty()) {
503         std::string tmp = d->FullPath;
504         std::string::size_type pos = tmp.rfind('.');
505         if (pos != std::string::npos && (tmp.substr(pos) != ".h")) {
506           tmp = tmp.substr(0, pos);
507           fprintf(fout, "%s\n", d->FullPath.c_str());
508         }
509       }
510       ListDependencies(d, fout, visited);
511     }
512   }
513 }
514 }