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"
12 #include "cmsys/FStream.hxx"
13 #include "cmsys/RegularExpression.hxx"
15 #include "cmExecutionStatus.h"
16 #include "cmGeneratorExpression.h"
17 #include "cmMakefile.h"
18 #include "cmSourceFile.h"
19 #include "cmStringAlgorithms.h"
20 #include "cmSystemTools.h"
25 /** \class cmDependInformation
26 * \brief Store dependency information for a single source file.
28 * This structure stores the depend information for a single source file.
30 class cmDependInformation
34 * Construct with dependency generation marked not done; instance
35 * not placed in cmMakefile's list.
37 cmDependInformation() = default;
40 * The set of files on which this one depends.
42 using DependencySetType = std::set<cmDependInformation*>;
43 DependencySetType DependencySet;
46 * This flag indicates whether dependency checking has been
47 * performed for this file.
49 bool DependDone = false;
52 * If this object corresponds to a cmSourceFile instance, this points
55 const cmSourceFile* SourceFile = nullptr;
58 * Full path to this file.
63 * Full path not including file name.
68 * Name used to #include this file.
70 std::string IncludeName;
73 * This method adds the dependencies of another file to this one.
75 void AddDependencies(cmDependInformation* info)
78 this->DependencySet.insert(info);
87 * Construct the object with verbose turned off.
91 this->Verbose = false;
92 this->IncludeFileRegularExpression.compile("^.*$");
93 this->ComplainFileRegularExpression.compile("^$");
99 ~cmLBDepend() = default;
101 cmLBDepend(const cmLBDepend&) = delete;
102 cmLBDepend& operator=(const cmLBDepend&) = delete;
105 * Set the makefile that is used as a source of classes.
107 void SetMakefile(cmMakefile* makefile)
109 this->Makefile = makefile;
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());
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");
126 std::string incDirs = cmGeneratorExpression::Preprocess(
127 *incDirProp, cmGeneratorExpression::StripAllGeneratorExpressions);
129 std::vector<std::string> includes = cmExpandedList(incDirs);
131 for (std::string& path : includes) {
132 this->Makefile->ExpandVariablesInString(path);
133 if (uniqueIncludes.insert(path).second) {
134 orderedAndUniqueIncludes.push_back(path);
139 for (std::string const& inc : orderedAndUniqueIncludes) {
140 this->AddSearchPath(inc);
145 * Add a directory to the search path for include files.
147 void AddSearchPath(const std::string& path)
149 this->IncludeDirectories.push_back(path);
153 * Generate dependencies for the file given. Returns a pointer to
154 * the cmDependInformation object for the file.
156 const cmDependInformation* FindDependencies(const std::string& file)
158 cmDependInformation* info = this->GetDependInformation(file, "");
159 this->GenerateDependInformation(info);
165 * Compute the depend information for this class.
168 void DependWalk(cmDependInformation* info)
170 cmsys::ifstream fin(info->FullPath.c_str());
172 cmSystemTools::Error("error can not open " + info->FullPath);
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);
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);
190 qend = line.find('>', qstart + 1);
192 qend = line.find('\"', qstart + 1);
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)) {
199 std::string message =
200 cmStrCat("Skipping ", includeFile, " for file ", info->FullPath);
201 cmSystemTools::Error(message);
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";
215 // try jumping to .cxx .cpp and .c in order
216 if (cmSystemTools::FileExists(cxxFile)) {
219 for (std::string const& path : this->IncludeDirectories) {
220 if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
225 cxxFile = root + ".cpp";
226 if (cmSystemTools::FileExists(cxxFile)) {
229 for (std::string const& path : this->IncludeDirectories) {
230 if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
236 cxxFile = root + ".c";
237 if (cmSystemTools::FileExists(cxxFile)) {
240 for (std::string const& path : this->IncludeDirectories) {
241 if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
247 cxxFile = root + ".txx";
248 if (cmSystemTools::FileExists(cxxFile)) {
251 for (std::string const& path : this->IncludeDirectories) {
252 if (cmSystemTools::FileExists(cmStrCat(path, "/", cxxFile))) {
258 this->AddDependency(info, cxxFile);
266 * Add a dependency. Possibly walk it for more dependencies.
268 void AddDependency(cmDependInformation* info, const std::string& file)
270 cmDependInformation* dependInfo =
271 this->GetDependInformation(file, info->PathOnly);
272 this->GenerateDependInformation(dependInfo);
273 info->AddDependencies(dependInfo);
277 * Fill in the given object with dependency information. If the
278 * information is already complete, nothing is done.
280 void GenerateDependInformation(cmDependInformation* info)
282 // If dependencies are already done, stop now.
283 if (info->DependDone) {
286 // Make sure we don't visit the same file more than once.
287 info->DependDone = true;
289 const std::string& path = info->FullPath;
291 cmSystemTools::Error(
292 "Attempt to find dependencies for file without path!");
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);
305 // See if the cmSourceFile for it has any files specified as
307 if (info->SourceFile != nullptr) {
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
313 if (!cFile.GetDepends().empty()) {
314 // Dependency hints have been given. Use them to begin the
316 for (std::string const& file : cFile.GetDepends()) {
317 this->AddDependency(info, file);
320 // Found dependency information. We are done.
326 // Try to find the file amongst the sources
327 cmSourceFile* srcFile = this->Makefile->GetSource(
328 cmSystemTools::GetFilenameWithoutExtension(path));
330 if (srcFile->ResolveFullPath() == path) {
333 // try to guess which include path to use
334 for (std::string incpath : this->IncludeDirectories) {
335 if (!incpath.empty() && incpath.back() != '/') {
339 if (srcFile->ResolveFullPath() == incpath) {
340 // set the path to the guessed path
341 info->FullPath = incpath;
350 // Couldn't find any dependency information.
351 if (this->ComplainFileRegularExpression.find(info->IncludeName)) {
352 cmSystemTools::Error("error cannot find dependencies for " + path);
354 // Destroy the name of the file so that it won't be output as a
356 info->FullPath.clear();
362 * Get an instance of cmDependInformation corresponding to the given file
365 cmDependInformation* GetDependInformation(const std::string& file,
366 const std::string& extraPath)
368 // Get the full path for the file so that lookup is unambiguous.
369 std::string fullPath = this->FullPath(file, extraPath);
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();
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);
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.
392 std::string FullPath(const std::string& fname, const std::string& extraPath)
394 auto m = this->DirectoryToFileToPathMap.find(extraPath);
396 if (m != this->DirectoryToFileToPathMap.end()) {
397 FileToPathMapType& map = m->second;
398 auto p = map.find(fname);
399 if (p != map.end()) {
404 if (cmSystemTools::FileExists(fname, true)) {
405 std::string fp = cmSystemTools::CollapseFullPath(fname);
406 this->DirectoryToFileToPathMap[extraPath][fname] = fp;
410 for (std::string path : this->IncludeDirectories) {
411 if (!path.empty() && path.back() != '/') {
415 if (cmSystemTools::FileExists(path, true) &&
416 !cmSystemTools::FileIsDirectory(path)) {
417 std::string fp = cmSystemTools::CollapseFullPath(path);
418 this->DirectoryToFileToPathMap[extraPath][fname] = fp;
423 if (!extraPath.empty()) {
424 std::string path = extraPath;
425 if (!path.empty() && path.back() != '/') {
429 if (cmSystemTools::FileExists(path, true) &&
430 !cmSystemTools::FileIsDirectory(path)) {
431 std::string fp = cmSystemTools::CollapseFullPath(path);
432 this->DirectoryToFileToPathMap[extraPath][fname] = fp;
437 // Couldn't find the file.
441 cmMakefile* Makefile;
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");
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
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);
480 FILE* fout = cmsys::SystemTools::Fopen(outputFile, "w");
482 status.SetError(cmStrCat("Can not open output file: ", outputFile));
485 std::set<cmDependInformation const*> visited;
486 ListDependencies(info, fout, &visited);
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);