Merge branch 'release-3.29'
[kiteware-cmake.git] / Source / cmOutputRequiredFilesCommand.cxx
blob75d15018ba2c4641041164df805373b699aa2d40
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"
5 #include <cstdio>
6 #include <map>
7 #include <set>
8 #include <unordered_map>
9 #include <utility>
11 #include <cm/memory>
13 #include "cmsys/FStream.hxx"
14 #include "cmsys/RegularExpression.hxx"
16 #include "cmExecutionStatus.h"
17 #include "cmGeneratorExpression.h"
18 #include "cmList.h"
19 #include "cmMakefile.h"
20 #include "cmSourceFile.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
23 #include "cmTarget.h"
24 #include "cmValue.h"
26 namespace {
27 /** \class cmDependInformation
28 * \brief Store dependency information for a single source file.
30 * This structure stores the depend information for a single source file.
32 class cmDependInformation
34 public:
35 /**
36 * Construct with dependency generation marked not done; instance
37 * not placed in cmMakefile's list.
39 cmDependInformation() = default;
41 /**
42 * The set of files on which this one depends.
44 using DependencySetType = std::set<cmDependInformation*>;
45 DependencySetType DependencySet;
47 /**
48 * This flag indicates whether dependency checking has been
49 * performed for this file.
51 bool DependDone = false;
53 /**
54 * If this object corresponds to a cmSourceFile instance, this points
55 * to it.
57 const cmSourceFile* SourceFile = nullptr;
59 /**
60 * Full path to this file.
62 std::string FullPath;
64 /**
65 * Full path not including file name.
67 std::string PathOnly;
69 /**
70 * Name used to #include this file.
72 std::string IncludeName;
74 /**
75 * This method adds the dependencies of another file to this one.
77 void AddDependencies(cmDependInformation* info)
79 if (this != info) {
80 this->DependencySet.insert(info);
85 class cmLBDepend
87 public:
88 /**
89 * Construct the object with verbose turned off.
91 cmLBDepend()
93 this->Verbose = false;
94 this->IncludeFileRegularExpression.compile("^.*$");
95 this->ComplainFileRegularExpression.compile("^$");
98 /**
99 * Destructor.
101 ~cmLBDepend() = default;
103 cmLBDepend(const cmLBDepend&) = delete;
104 cmLBDepend& operator=(const cmLBDepend&) = delete;
107 * Set the makefile that is used as a source of classes.
109 void SetMakefile(cmMakefile* makefile)
111 this->Makefile = makefile;
113 // Now extract the include file regular expression from the makefile.
114 this->IncludeFileRegularExpression.compile(
115 this->Makefile->GetIncludeRegularExpression());
116 this->ComplainFileRegularExpression.compile(
117 this->Makefile->GetComplainRegularExpression());
119 // Now extract any include paths from the targets
120 std::set<std::string> uniqueIncludes;
121 std::vector<std::string> orderedAndUniqueIncludes;
122 for (auto const& target : this->Makefile->GetTargets()) {
123 cmValue incDirProp = target.second.GetProperty("INCLUDE_DIRECTORIES");
124 if (!incDirProp) {
125 continue;
128 std::string incDirs = cmGeneratorExpression::Preprocess(
129 *incDirProp, cmGeneratorExpression::StripAllGeneratorExpressions);
131 cmList includes{ incDirs };
133 for (auto& path : includes) {
134 this->Makefile->ExpandVariablesInString(path);
135 if (uniqueIncludes.insert(path).second) {
136 orderedAndUniqueIncludes.push_back(path);
141 for (std::string const& inc : orderedAndUniqueIncludes) {
142 this->AddSearchPath(inc);
147 * Add a directory to the search path for include files.
149 void AddSearchPath(const std::string& path)
151 this->IncludeDirectories.push_back(path);
155 * Generate dependencies for the file given. Returns a pointer to
156 * the cmDependInformation object for the file.
158 const cmDependInformation* FindDependencies(const std::string& file)
160 cmDependInformation* info = this->GetDependInformation(file, "");
161 this->GenerateDependInformation(info);
162 return info;
165 protected:
167 * Compute the depend information for this class.
170 void DependWalk(cmDependInformation* info)
172 cmsys::ifstream fin(info->FullPath.c_str());
173 if (!fin) {
174 cmSystemTools::Error("error can not open " + info->FullPath);
175 return;
178 std::string line;
179 while (cmSystemTools::GetLineFromStream(fin, line)) {
180 if (cmHasLiteralPrefix(line, "#include")) {
181 // if it is an include line then create a string class
182 size_t qstart = line.find('\"', 8);
183 size_t qend;
184 // if a quote is not found look for a <
185 if (qstart == std::string::npos) {
186 qstart = line.find('<', 8);
187 // if a < is not found then move on
188 if (qstart == std::string::npos) {
189 cmSystemTools::Error("unknown include directive " + line);
190 continue;
192 qend = line.find('>', qstart + 1);
193 } else {
194 qend = line.find('\"', qstart + 1);
196 // extract the file being included
197 std::string includeFile = line.substr(qstart + 1, qend - qstart - 1);
198 // see if the include matches the regular expression
199 if (!this->IncludeFileRegularExpression.find(includeFile)) {
200 if (this->Verbose) {
201 std::string message =
202 cmStrCat("Skipping ", includeFile, " for file ", info->FullPath);
203 cmSystemTools::Error(message);
205 continue;
208 // Add this file and all its dependencies.
209 this->AddDependency(info, includeFile);
210 /// add the cxx file if it exists
211 std::string cxxFile = includeFile;
212 std::string::size_type pos = cxxFile.rfind('.');
213 if (pos != std::string::npos) {
214 std::string root = cxxFile.substr(0, pos);
215 cxxFile = root + ".cxx";
216 bool found = false;
217 // try jumping to .cxx .cpp and .c in order
218 if (cmSystemTools::FileExists(cxxFile)) {
219 found = true;
221 for (std::string const& path : this->IncludeDirectories) {
222 if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
223 found = true;
226 if (!found) {
227 cxxFile = root + ".cpp";
228 if (cmSystemTools::FileExists(cxxFile)) {
229 found = true;
231 for (std::string const& path : this->IncludeDirectories) {
232 if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
233 found = true;
237 if (!found) {
238 cxxFile = root + ".c";
239 if (cmSystemTools::FileExists(cxxFile)) {
240 found = true;
242 for (std::string const& path : this->IncludeDirectories) {
243 if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
244 found = true;
248 if (!found) {
249 cxxFile = root + ".txx";
250 if (cmSystemTools::FileExists(cxxFile)) {
251 found = true;
253 for (std::string const& path : this->IncludeDirectories) {
254 if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
255 found = true;
259 if (found) {
260 this->AddDependency(info, cxxFile);
268 * Add a dependency. Possibly walk it for more dependencies.
270 void AddDependency(cmDependInformation* info, const std::string& file)
272 cmDependInformation* dependInfo =
273 this->GetDependInformation(file, info->PathOnly);
274 this->GenerateDependInformation(dependInfo);
275 info->AddDependencies(dependInfo);
279 * Fill in the given object with dependency information. If the
280 * information is already complete, nothing is done.
282 void GenerateDependInformation(cmDependInformation* info)
284 // If dependencies are already done, stop now.
285 if (info->DependDone) {
286 return;
288 // Make sure we don't visit the same file more than once.
289 info->DependDone = true;
291 const std::string& path = info->FullPath;
292 if (path.empty()) {
293 cmSystemTools::Error(
294 "Attempt to find dependencies for file without path!");
295 return;
298 bool found = false;
300 // If the file exists, use it to find dependency information.
301 if (cmSystemTools::FileExists(path, true)) {
302 // Use the real file to find its dependencies.
303 this->DependWalk(info);
304 found = true;
307 // See if the cmSourceFile for it has any files specified as
308 // dependency hints.
309 if (info->SourceFile != nullptr) {
311 // Get the cmSourceFile corresponding to this.
312 const cmSourceFile& cFile = *(info->SourceFile);
313 // See if there are any hints for finding dependencies for the missing
314 // file.
315 if (!cFile.GetDepends().empty()) {
316 // Dependency hints have been given. Use them to begin the
317 // recursion.
318 for (std::string const& file : cFile.GetDepends()) {
319 this->AddDependency(info, file);
322 // Found dependency information. We are done.
323 found = true;
327 if (!found) {
328 // Try to find the file amongst the sources
329 cmSourceFile* srcFile = this->Makefile->GetSource(
330 cmSystemTools::GetFilenameWithoutExtension(path));
331 if (srcFile) {
332 if (srcFile->ResolveFullPath() == path) {
333 found = true;
334 } else {
335 // try to guess which include path to use
336 for (std::string incpath : this->IncludeDirectories) {
337 if (!incpath.empty() && incpath.back() != '/') {
338 incpath += "/";
340 incpath += path;
341 if (srcFile->ResolveFullPath() == incpath) {
342 // set the path to the guessed path
343 info->FullPath = incpath;
344 found = true;
351 if (!found) {
352 // Couldn't find any dependency information.
353 if (this->ComplainFileRegularExpression.find(info->IncludeName)) {
354 cmSystemTools::Error("error cannot find dependencies for " + path);
355 } else {
356 // Destroy the name of the file so that it won't be output as a
357 // dependency.
358 info->FullPath.clear();
364 * Get an instance of cmDependInformation corresponding to the given file
365 * name.
367 cmDependInformation* GetDependInformation(const std::string& file,
368 const std::string& extraPath)
370 // Get the full path for the file so that lookup is unambiguous.
371 std::string fullPath = this->FullPath(file, extraPath);
373 // Try to find the file's instance of cmDependInformation.
374 auto result = this->DependInformationMap.find(fullPath);
375 if (result != this->DependInformationMap.end()) {
376 // Found an instance, return it.
377 return result->second.get();
379 // Didn't find an instance. Create a new one and save it.
380 auto info = cm::make_unique<cmDependInformation>();
381 auto* ptr = info.get();
382 info->FullPath = fullPath;
383 info->PathOnly = cmSystemTools::GetFilenamePath(fullPath);
384 info->IncludeName = file;
385 this->DependInformationMap[fullPath] = std::move(info);
386 return ptr;
390 * Find the full path name for the given file name.
391 * This uses the include directories.
392 * TODO: Cache path conversions to reduce FileExists calls.
394 std::string FullPath(const std::string& fname, const std::string& extraPath)
396 auto m = this->DirectoryToFileToPathMap.find(extraPath);
398 if (m != this->DirectoryToFileToPathMap.end()) {
399 FileToPathMapType& map = m->second;
400 auto p = map.find(fname);
401 if (p != map.end()) {
402 return p->second;
406 if (cmSystemTools::FileExists(fname, true)) {
407 std::string fp = cmSystemTools::CollapseFullPath(fname);
408 this->DirectoryToFileToPathMap[extraPath][fname] = fp;
409 return fp;
412 for (std::string path : this->IncludeDirectories) {
413 if (!path.empty() && path.back() != '/') {
414 path += "/";
416 path += fname;
417 if (cmSystemTools::FileExists(path, true)) {
418 std::string fp = cmSystemTools::CollapseFullPath(path);
419 this->DirectoryToFileToPathMap[extraPath][fname] = fp;
420 return fp;
424 if (!extraPath.empty()) {
425 std::string path = extraPath;
426 if (!path.empty() && path.back() != '/') {
427 path = path + "/";
429 path = path + fname;
430 if (cmSystemTools::FileExists(path, true)) {
431 std::string fp = cmSystemTools::CollapseFullPath(path);
432 this->DirectoryToFileToPathMap[extraPath][fname] = fp;
433 return fp;
437 // Couldn't find the file.
438 return fname;
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;
455 void ListDependencies(cmDependInformation const* info, FILE* fout,
456 std::set<cmDependInformation const*>* visited);
459 // cmOutputRequiredFilesCommand
460 bool cmOutputRequiredFilesCommand(std::vector<std::string> const& args,
461 cmExecutionStatus& status)
463 if (args.size() != 2) {
464 status.SetError("called with incorrect number of arguments");
465 return false;
468 // store the arg for final pass
469 const std::string& file = args[0];
470 const std::string& outputFile = args[1];
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;
485 std::set<cmDependInformation const*> visited;
486 ListDependencies(info, fout, &visited);
487 fclose(fout);
490 return true;
493 namespace {
494 void ListDependencies(cmDependInformation const* info, FILE* fout,
495 std::set<cmDependInformation const*>* visited)
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());
510 ListDependencies(d, fout, visited);