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"
8 #include <unordered_map>
13 #include "cmsys/FStream.hxx"
14 #include "cmsys/RegularExpression.hxx"
16 #include "cmExecutionStatus.h"
17 #include "cmGeneratorExpression.h"
19 #include "cmMakefile.h"
20 #include "cmSourceFile.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
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
36 * Construct with dependency generation marked not done; instance
37 * not placed in cmMakefile's list.
39 cmDependInformation() = default;
42 * The set of files on which this one depends.
44 using DependencySetType
= std::set
<cmDependInformation
*>;
45 DependencySetType DependencySet
;
48 * This flag indicates whether dependency checking has been
49 * performed for this file.
51 bool DependDone
= false;
54 * If this object corresponds to a cmSourceFile instance, this points
57 const cmSourceFile
* SourceFile
= nullptr;
60 * Full path to this file.
65 * Full path not including file name.
70 * Name used to #include this file.
72 std::string IncludeName
;
75 * This method adds the dependencies of another file to this one.
77 void AddDependencies(cmDependInformation
* info
)
80 this->DependencySet
.insert(info
);
89 * Construct the object with verbose turned off.
93 this->Verbose
= false;
94 this->IncludeFileRegularExpression
.compile("^.*$");
95 this->ComplainFileRegularExpression
.compile("^$");
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");
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
);
167 * Compute the depend information for this class.
170 void DependWalk(cmDependInformation
* info
)
172 cmsys::ifstream
fin(info
->FullPath
.c_str());
174 cmSystemTools::Error("error can not open " + info
->FullPath
);
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);
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
);
192 qend
= line
.find('>', qstart
+ 1);
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
)) {
201 std::string message
=
202 cmStrCat("Skipping ", includeFile
, " for file ", info
->FullPath
);
203 cmSystemTools::Error(message
);
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";
217 // try jumping to .cxx .cpp and .c in order
218 if (cmSystemTools::FileExists(cxxFile
)) {
221 for (std::string
const& path
: this->IncludeDirectories
) {
222 if (cmSystemTools::FileExists(cmStrCat(path
, "/", cxxFile
))) {
227 cxxFile
= root
+ ".cpp";
228 if (cmSystemTools::FileExists(cxxFile
)) {
231 for (std::string
const& path
: this->IncludeDirectories
) {
232 if (cmSystemTools::FileExists(cmStrCat(path
, "/", cxxFile
))) {
238 cxxFile
= root
+ ".c";
239 if (cmSystemTools::FileExists(cxxFile
)) {
242 for (std::string
const& path
: this->IncludeDirectories
) {
243 if (cmSystemTools::FileExists(cmStrCat(path
, "/", cxxFile
))) {
249 cxxFile
= root
+ ".txx";
250 if (cmSystemTools::FileExists(cxxFile
)) {
253 for (std::string
const& path
: this->IncludeDirectories
) {
254 if (cmSystemTools::FileExists(cmStrCat(path
, "/", cxxFile
))) {
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
) {
288 // Make sure we don't visit the same file more than once.
289 info
->DependDone
= true;
291 const std::string
& path
= info
->FullPath
;
293 cmSystemTools::Error(
294 "Attempt to find dependencies for file without path!");
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
);
307 // See if the cmSourceFile for it has any files specified as
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
315 if (!cFile
.GetDepends().empty()) {
316 // Dependency hints have been given. Use them to begin the
318 for (std::string
const& file
: cFile
.GetDepends()) {
319 this->AddDependency(info
, file
);
322 // Found dependency information. We are done.
328 // Try to find the file amongst the sources
329 cmSourceFile
* srcFile
= this->Makefile
->GetSource(
330 cmSystemTools::GetFilenameWithoutExtension(path
));
332 if (srcFile
->ResolveFullPath() == path
) {
335 // try to guess which include path to use
336 for (std::string incpath
: this->IncludeDirectories
) {
337 if (!incpath
.empty() && incpath
.back() != '/') {
341 if (srcFile
->ResolveFullPath() == incpath
) {
342 // set the path to the guessed path
343 info
->FullPath
= incpath
;
352 // Couldn't find any dependency information.
353 if (this->ComplainFileRegularExpression
.find(info
->IncludeName
)) {
354 cmSystemTools::Error("error cannot find dependencies for " + path
);
356 // Destroy the name of the file so that it won't be output as a
358 info
->FullPath
.clear();
364 * Get an instance of cmDependInformation corresponding to the given file
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
);
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()) {
406 if (cmSystemTools::FileExists(fname
, true)) {
407 std::string fp
= cmSystemTools::CollapseFullPath(fname
);
408 this->DirectoryToFileToPathMap
[extraPath
][fname
] = fp
;
412 for (std::string path
: this->IncludeDirectories
) {
413 if (!path
.empty() && path
.back() != '/') {
417 if (cmSystemTools::FileExists(path
, true)) {
418 std::string fp
= cmSystemTools::CollapseFullPath(path
);
419 this->DirectoryToFileToPathMap
[extraPath
][fname
] = fp
;
424 if (!extraPath
.empty()) {
425 std::string path
= extraPath
;
426 if (!path
.empty() && path
.back() != '/') {
430 if (cmSystemTools::FileExists(path
, true)) {
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
);