1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: cmDependsC.cxx,v $
6 Date: $Date: 2008-05-14 15:54:32 $
7 Version: $Revision: 1.35 $
9 Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
10 See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
12 This software is distributed WITHOUT ANY WARRANTY; without even
13 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 PURPOSE. See the above copyright notices for more information.
16 =========================================================================*/
17 #include "cmDependsC.h"
19 #include "cmFileTimeComparison.h"
20 #include "cmLocalGenerator.h"
21 #include "cmMakefile.h"
22 #include "cmSystemTools.h"
24 #include <ctype.h> // isspace
27 #define INCLUDE_REGEX_LINE \
28 "^[ \t]*#[ \t]*(include|import)[ \t]*[<\"]([^\">]+)([\">])"
30 #define INCLUDE_REGEX_LINE_MARKER "#IncludeRegexLine: "
31 #define INCLUDE_REGEX_SCAN_MARKER "#IncludeRegexScan: "
32 #define INCLUDE_REGEX_COMPLAIN_MARKER "#IncludeRegexComplain: "
33 #define INCLUDE_REGEX_TRANSFORM_MARKER "#IncludeRegexTransform: "
35 //----------------------------------------------------------------------------
36 cmDependsC::cmDependsC()
40 //----------------------------------------------------------------------------
41 cmDependsC::cmDependsC(cmLocalGenerator
* lg
, const char* targetDir
,
42 const char* lang
): cmDepends(lg
, targetDir
)
44 cmMakefile
* mf
= lg
->GetMakefile();
46 // Configure the include file search path.
47 this->SetIncludePathFromLanguage(lang
);
49 // Configure regular expressions.
50 std::string scanRegex
= "^.*$";
51 std::string complainRegex
= "^$";
53 std::string scanRegexVar
= "CMAKE_";
55 scanRegexVar
+= "_INCLUDE_REGEX_SCAN";
56 if(const char* sr
= mf
->GetDefinition(scanRegexVar
.c_str()))
60 std::string complainRegexVar
= "CMAKE_";
61 complainRegexVar
+= lang
;
62 complainRegexVar
+= "_INCLUDE_REGEX_COMPLAIN";
63 if(const char* cr
= mf
->GetDefinition(complainRegexVar
.c_str()))
69 this->IncludeRegexLine
.compile(INCLUDE_REGEX_LINE
);
70 this->IncludeRegexScan
.compile(scanRegex
.c_str());
71 this->IncludeRegexComplain
.compile(complainRegex
.c_str());
72 this->IncludeRegexLineString
= INCLUDE_REGEX_LINE_MARKER INCLUDE_REGEX_LINE
;
73 this->IncludeRegexScanString
= INCLUDE_REGEX_SCAN_MARKER
;
74 this->IncludeRegexScanString
+= scanRegex
;
75 this->IncludeRegexComplainString
= INCLUDE_REGEX_COMPLAIN_MARKER
;
76 this->IncludeRegexComplainString
+= complainRegex
;
78 this->SetupTransforms();
80 this->CacheFileName
= this->TargetDirectory
;
81 this->CacheFileName
+= "/";
82 this->CacheFileName
+= lang
;
83 this->CacheFileName
+= ".includecache";
85 this->ReadCacheFile();
88 //----------------------------------------------------------------------------
89 cmDependsC::~cmDependsC()
91 this->WriteCacheFile();
93 for (std::map
<cmStdString
, cmIncludeLines
*>::iterator it
=
94 this->FileCache
.begin(); it
!=this->FileCache
.end(); ++it
)
100 //----------------------------------------------------------------------------
101 bool cmDependsC::WriteDependencies(const char *src
, const char *obj
,
102 std::ostream
& makeDepends
, std::ostream
& internalDepends
)
104 // Make sure this is a scanning instance.
105 if(!src
|| src
[0] == '\0')
107 cmSystemTools::Error("Cannot scan dependencies without a source file.");
110 if(!obj
|| obj
[0] == '\0')
112 cmSystemTools::Error("Cannot scan dependencies without an object file.");
116 // Walk the dependency graph starting with the source file.
120 this->Unscanned
.push(root
);
121 this->Encountered
.clear();
122 this->Encountered
.insert(src
);
123 std::set
<cmStdString
> dependencies
;
124 std::set
<cmStdString
> scanned
;
126 // Use reserve to allocate enough memory for both strings,
127 // so that during the loops no memory is allocated or freed
128 std::string cacheKey
;
129 cacheKey
.reserve(4*1024);
130 std::string tempPathStr
;
131 tempPathStr
.reserve(4*1024);
133 while(!this->Unscanned
.empty())
135 // Get the next file to scan.
136 UnscannedEntry current
= this->Unscanned
.front();
137 this->Unscanned
.pop();
139 // If not a full path, find the file in the include path.
140 std::string fullName
;
141 if(first
|| cmSystemTools::FileIsFullPath(current
.FileName
.c_str()))
143 if(cmSystemTools::FileExists(current
.FileName
.c_str(), true))
145 fullName
= current
.FileName
;
148 else if(!current
.QuotedLocation
.empty() &&
149 cmSystemTools::FileExists(current
.QuotedLocation
.c_str(), true))
151 // The include statement producing this entry was a double-quote
152 // include and the included file is present in the directory of
153 // the source containing the include statement.
154 fullName
= current
.QuotedLocation
;
158 // With GCC distribution of STL, assigning to a string directly
159 // throws away the internal buffer of the left-hand-side. We
160 // want to keep the pre-allocated buffer so we use C-style
161 // string assignment and then operator+=. We could call
162 // .clear() instead of assigning to an empty string but the
163 // method does not exist on some older compilers.
165 cacheKey
+= current
.FileName
;
167 for(std::vector
<std::string
>::const_iterator i
=
168 this->IncludePath
.begin(); i
!= this->IncludePath
.end(); ++i
)
172 std::map
<cmStdString
, cmStdString
>::iterator
173 headerLocationIt
=this->HeaderLocationCache
.find(cacheKey
);
174 if (headerLocationIt
!=this->HeaderLocationCache
.end())
176 fullName
=headerLocationIt
->second
;
178 else for(std::vector
<std::string
>::const_iterator i
=
179 this->IncludePath
.begin(); i
!= this->IncludePath
.end(); ++i
)
181 // Construct the name of the file as if it were in the current
182 // include directory. Avoid using a leading "./".
187 tempPathStr
+= current
.FileName
;
193 tempPathStr
+=current
.FileName
;
196 // Look for the file in this location.
197 if(cmSystemTools::FileExists(tempPathStr
.c_str(), true))
199 fullName
= tempPathStr
;
200 HeaderLocationCache
[cacheKey
]=fullName
;
206 // Complain if the file cannot be found and matches the complain
208 if(fullName
.empty() &&
209 this->IncludeRegexComplain
.find(current
.FileName
.c_str()))
211 cmSystemTools::Error("Cannot find file \"",
212 current
.FileName
.c_str(), "\".");
216 // Scan the file if it was found and has not been scanned already.
217 if(!fullName
.empty() && (scanned
.find(fullName
) == scanned
.end()))
219 // Record scanned files.
220 scanned
.insert(fullName
);
222 // Check whether this file is already in the cache
223 std::map
<cmStdString
, cmIncludeLines
*>::iterator fileIt
=
224 this->FileCache
.find(fullName
);
225 if (fileIt
!=this->FileCache
.end())
227 fileIt
->second
->Used
=true;
228 dependencies
.insert(fullName
);
229 for (std::vector
<UnscannedEntry
>::const_iterator incIt
=
230 fileIt
->second
->UnscannedEntries
.begin();
231 incIt
!=fileIt
->second
->UnscannedEntries
.end(); ++incIt
)
233 if (this->Encountered
.find(incIt
->FileName
) ==
234 this->Encountered
.end())
236 this->Encountered
.insert(incIt
->FileName
);
237 this->Unscanned
.push(*incIt
);
244 // Try to scan the file. Just leave it out if we cannot find
246 std::ifstream
fin(fullName
.c_str());
249 // Add this file as a dependency.
250 dependencies
.insert(fullName
);
252 // Scan this file for new dependencies. Pass the directory
253 // containing the file to handle double-quote includes.
254 std::string dir
= cmSystemTools::GetFilenamePath(fullName
);
255 this->Scan(fin
, dir
.c_str(), fullName
);
263 // Write the dependencies to the output stream. Makefile rules
264 // written by the original local generator for this directory
265 // convert the dependencies to paths relative to the home output
266 // directory. We must do the same here.
267 internalDepends
<< obj
<< std::endl
;
268 for(std::set
<cmStdString
>::iterator i
=dependencies
.begin();
269 i
!= dependencies
.end(); ++i
)
271 makeDepends
<< obj
<< ": " <<
272 this->LocalGenerator
->Convert(i
->c_str(),
273 cmLocalGenerator::HOME_OUTPUT
,
274 cmLocalGenerator::MAKEFILE
)
276 internalDepends
<< " " << i
->c_str() << std::endl
;
278 makeDepends
<< std::endl
;
283 //----------------------------------------------------------------------------
284 void cmDependsC::ReadCacheFile()
286 if(this->CacheFileName
.size() == 0)
290 std::ifstream
fin(this->CacheFileName
.c_str());
297 cmIncludeLines
* cacheEntry
=0;
298 bool haveFileName
=false;
300 while(cmSystemTools::GetLineFromStream(fin
, line
))
308 //the first line after an empty line is the name of the parsed file
309 if (haveFileName
==false)
313 cmFileTimeComparison comp
;
314 bool res
=comp
.FileTimeCompare(this->CacheFileName
.c_str(),
315 line
.c_str(), &newer
);
317 if ((res
==true) && (newer
==1)) //cache is newer than the parsed file
319 cacheEntry
=new cmIncludeLines
;
320 this->FileCache
[line
]=cacheEntry
;
322 // file doesn't exist, check that the regular expressions
326 if (line
.find(INCLUDE_REGEX_LINE_MARKER
) == 0)
328 if (line
!= this->IncludeRegexLineString
)
333 else if (line
.find(INCLUDE_REGEX_SCAN_MARKER
) == 0)
335 if (line
!= this->IncludeRegexScanString
)
340 else if (line
.find(INCLUDE_REGEX_COMPLAIN_MARKER
) == 0)
342 if (line
!= this->IncludeRegexComplainString
)
347 else if (line
.find(INCLUDE_REGEX_TRANSFORM_MARKER
) == 0)
349 if (line
!= this->IncludeRegexTransformString
)
356 else if (cacheEntry
!=0)
358 UnscannedEntry entry
;
359 entry
.FileName
= line
;
360 if (cmSystemTools::GetLineFromStream(fin
, line
))
364 entry
.QuotedLocation
=line
;
366 cacheEntry
->UnscannedEntries
.push_back(entry
);
372 //----------------------------------------------------------------------------
373 void cmDependsC::WriteCacheFile() const
375 if(this->CacheFileName
.size() == 0)
379 std::ofstream
cacheOut(this->CacheFileName
.c_str());
385 cacheOut
<< this->IncludeRegexLineString
<< "\n\n";
386 cacheOut
<< this->IncludeRegexScanString
<< "\n\n";
387 cacheOut
<< this->IncludeRegexComplainString
<< "\n\n";
388 cacheOut
<< this->IncludeRegexTransformString
<< "\n\n";
390 for (std::map
<cmStdString
, cmIncludeLines
*>::const_iterator fileIt
=
391 this->FileCache
.begin();
392 fileIt
!=this->FileCache
.end(); ++fileIt
)
394 if (fileIt
->second
->Used
)
396 cacheOut
<<fileIt
->first
.c_str()<<std::endl
;
398 for (std::vector
<UnscannedEntry
>::const_iterator
399 incIt
=fileIt
->second
->UnscannedEntries
.begin();
400 incIt
!=fileIt
->second
->UnscannedEntries
.end(); ++incIt
)
402 cacheOut
<<incIt
->FileName
.c_str()<<std::endl
;
403 if (incIt
->QuotedLocation
.empty())
405 cacheOut
<<"-"<<std::endl
;
409 cacheOut
<<incIt
->QuotedLocation
.c_str()<<std::endl
;
417 //----------------------------------------------------------------------------
418 void cmDependsC::Scan(std::istream
& is
, const char* directory
,
419 const cmStdString
& fullName
)
421 cmIncludeLines
* newCacheEntry
=new cmIncludeLines
;
422 newCacheEntry
->Used
=true;
423 this->FileCache
[fullName
]=newCacheEntry
;
425 // Read one line at a time.
427 while(cmSystemTools::GetLineFromStream(is
, line
))
429 // Transform the line content first.
430 if(!this->TransformRules
.empty())
432 this->TransformLine(line
);
435 // Match include directives.
436 if(this->IncludeRegexLine
.find(line
.c_str()))
438 // Get the file being included.
439 UnscannedEntry entry
;
440 entry
.FileName
= this->IncludeRegexLine
.match(2);
441 if(this->IncludeRegexLine
.match(3) == "\"" &&
442 !cmSystemTools::FileIsFullPath(entry
.FileName
.c_str()))
444 // This was a double-quoted include with a relative path. We
445 // must check for the file in the directory containing the
446 // file we are scanning.
447 entry
.QuotedLocation
= directory
;
448 entry
.QuotedLocation
+= "/";
449 entry
.QuotedLocation
+= entry
.FileName
;
452 // Queue the file if it has not yet been encountered and it
453 // matches the regular expression for recursive scanning. Note
454 // that this check does not account for the possibility of two
455 // headers with the same name in different directories when one
456 // is included by double-quotes and the other by angle brackets.
457 // This kind of problem will be fixed when a more
458 // preprocessor-like implementation of this scanner is created.
459 if (this->IncludeRegexScan
.find(entry
.FileName
.c_str()))
461 newCacheEntry
->UnscannedEntries
.push_back(entry
);
462 if(this->Encountered
.find(entry
.FileName
) == this->Encountered
.end())
464 this->Encountered
.insert(entry
.FileName
);
465 this->Unscanned
.push(entry
);
472 //----------------------------------------------------------------------------
473 void cmDependsC::SetupTransforms()
475 // Get the transformation rules.
476 std::vector
<std::string
> transformRules
;
477 cmMakefile
* mf
= this->LocalGenerator
->GetMakefile();
478 if(const char* xform
=
479 mf
->GetDefinition("CMAKE_INCLUDE_TRANSFORMS"))
481 cmSystemTools::ExpandListArgument(xform
, transformRules
, true);
483 for(std::vector
<std::string
>::const_iterator tri
= transformRules
.begin();
484 tri
!= transformRules
.end(); ++tri
)
486 this->ParseTransform(*tri
);
489 this->IncludeRegexTransformString
= INCLUDE_REGEX_TRANSFORM_MARKER
;
490 if(!this->TransformRules
.empty())
492 // Construct the regular expression to match lines to be
494 std::string xform
= "^([ \t]*#[ \t]*(include|import)[ \t]*)(";
495 const char* sep
= "";
496 for(TransformRulesType::const_iterator tri
= this->TransformRules
.begin();
497 tri
!= this->TransformRules
.end(); ++tri
)
503 xform
+= ")[ \t]*\\(([^),]*)\\)";
504 this->IncludeRegexTransform
.compile(xform
.c_str());
506 // Build a string that encodes all transformation rules and will
507 // change when rules are changed.
508 this->IncludeRegexTransformString
+= xform
;
509 for(TransformRulesType::const_iterator tri
= this->TransformRules
.begin();
510 tri
!= this->TransformRules
.end(); ++tri
)
512 this->IncludeRegexTransformString
+= " ";
513 this->IncludeRegexTransformString
+= tri
->first
;
514 this->IncludeRegexTransformString
+= "(%)=";
515 this->IncludeRegexTransformString
+= tri
->second
;
520 //----------------------------------------------------------------------------
521 void cmDependsC::ParseTransform(std::string
const& xform
)
523 // A transform rule is of the form SOME_MACRO(%)=value-with-%
524 // We can simply separate with "(%)=".
525 std::string::size_type pos
= xform
.find("(%)=");
526 if(pos
== xform
.npos
|| pos
== 0)
530 std::string name
= xform
.substr(0, pos
);
531 std::string value
= xform
.substr(pos
+4, xform
.npos
);
532 this->TransformRules
[name
] = value
;
535 //----------------------------------------------------------------------------
536 void cmDependsC::TransformLine(std::string
& line
)
538 // Check for a transform rule match. Return if none.
539 if(!this->IncludeRegexTransform
.find(line
.c_str()))
543 TransformRulesType::const_iterator tri
=
544 this->TransformRules
.find(this->IncludeRegexTransform
.match(3));
545 if(tri
== this->TransformRules
.end())
550 // Construct the transformed line.
551 std::string newline
= this->IncludeRegexTransform
.match(1);
552 std::string arg
= this->IncludeRegexTransform
.match(4);
553 for(const char* c
= tri
->second
.c_str(); *c
; ++c
)
565 // Return the transformed line.