1 /*=========================================================================
3 Program: CMake - Cross-Platform Makefile Generator
4 Module: $RCSfile: cmDependsC.cxx,v $
6 Date: $Date: 2007/12/15 01:31:27 $
7 Version: $Revision: 1.33 $
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 "cmSystemTools.h"
23 #include <ctype.h> // isspace
26 #define INCLUDE_REGEX_LINE \
27 "^[ \t]*#[ \t]*(include|import)[ \t]*[<\"]([^\">]+)([\">])"
29 #define INCLUDE_REGEX_LINE_MARKER "#IncludeRegexLine: "
30 #define INCLUDE_REGEX_SCAN_MARKER "#IncludeRegexScan: "
31 #define INCLUDE_REGEX_COMPLAIN_MARKER "#IncludeRegexComplain: "
33 //----------------------------------------------------------------------------
34 cmDependsC::cmDependsC():
38 //----------------------------------------------------------------------------
39 // yummy look at all those constructor arguments
40 cmDependsC::cmDependsC(std::vector
<std::string
> const& includes
,
41 const char* scanRegex
, const char* complainRegex
,
42 const cmStdString
& cacheFileName
):
43 IncludePath(&includes
),
44 IncludeRegexLine(INCLUDE_REGEX_LINE
),
45 IncludeRegexScan(scanRegex
),
46 IncludeRegexComplain(complainRegex
),
47 IncludeRegexLineString(INCLUDE_REGEX_LINE_MARKER INCLUDE_REGEX_LINE
),
48 IncludeRegexScanString(std::string(INCLUDE_REGEX_SCAN_MARKER
)+scanRegex
),
49 IncludeRegexComplainString(
50 std::string(INCLUDE_REGEX_COMPLAIN_MARKER
)+complainRegex
),
51 CacheFileName(cacheFileName
)
53 this->ReadCacheFile();
56 //----------------------------------------------------------------------------
57 cmDependsC::~cmDependsC()
59 this->WriteCacheFile();
61 for (std::map
<cmStdString
, cmIncludeLines
*>::iterator it
=
62 this->FileCache
.begin(); it
!=this->FileCache
.end(); ++it
)
68 //----------------------------------------------------------------------------
69 bool cmDependsC::WriteDependencies(const char *src
, const char *obj
,
70 std::ostream
& makeDepends
, std::ostream
& internalDepends
)
72 // Make sure this is a scanning instance.
73 if(!src
|| src
[0] == '\0')
75 cmSystemTools::Error("Cannot scan dependencies without a source file.");
78 if(!obj
|| obj
[0] == '\0')
80 cmSystemTools::Error("Cannot scan dependencies without an object file.");
83 if(!this->IncludePath
)
85 cmSystemTools::Error("Cannot scan dependencies without an include path.");
89 // Walk the dependency graph starting with the source file.
93 this->Unscanned
.push(root
);
94 this->Encountered
.clear();
95 this->Encountered
.insert(src
);
96 std::set
<cmStdString
> dependencies
;
97 std::set
<cmStdString
> scanned
;
99 // Use reserve to allocate enough memory for both strings,
100 // so that during the loops no memory is allocated or freed
101 std::string cacheKey
;
102 cacheKey
.reserve(4*1024);
103 std::string tempPathStr
;
104 tempPathStr
.reserve(4*1024);
106 while(!this->Unscanned
.empty())
108 // Get the next file to scan.
109 UnscannedEntry current
= this->Unscanned
.front();
110 this->Unscanned
.pop();
112 // If not a full path, find the file in the include path.
113 std::string fullName
;
114 if(first
|| cmSystemTools::FileIsFullPath(current
.FileName
.c_str()))
116 if(cmSystemTools::FileExists(current
.FileName
.c_str(), true))
118 fullName
= current
.FileName
;
121 else if(!current
.QuotedLocation
.empty() &&
122 cmSystemTools::FileExists(current
.QuotedLocation
.c_str(), true))
124 // The include statement producing this entry was a double-quote
125 // include and the included file is present in the directory of
126 // the source containing the include statement.
127 fullName
= current
.QuotedLocation
;
131 // With GCC distribution of STL, assigning to a string directly
132 // throws away the internal buffer of the left-hand-side. We
133 // want to keep the pre-allocated buffer so we use C-style
134 // string assignment and then operator+=. We could call
135 // .clear() instead of assigning to an empty string but the
136 // method does not exist on some older compilers.
138 cacheKey
+= current
.FileName
;
140 for(std::vector
<std::string
>::const_iterator i
=
141 this->IncludePath
->begin(); i
!= this->IncludePath
->end(); ++i
)
145 std::map
<cmStdString
, cmStdString
>::iterator
146 headerLocationIt
=this->HeaderLocationCache
.find(cacheKey
);
147 if (headerLocationIt
!=this->HeaderLocationCache
.end())
149 fullName
=headerLocationIt
->second
;
151 else for(std::vector
<std::string
>::const_iterator i
=
152 this->IncludePath
->begin(); i
!= this->IncludePath
->end(); ++i
)
154 // Construct the name of the file as if it were in the current
155 // include directory. Avoid using a leading "./".
160 tempPathStr
+= current
.FileName
;
166 tempPathStr
+=current
.FileName
;
169 // Look for the file in this location.
170 if(cmSystemTools::FileExists(tempPathStr
.c_str(), true))
172 fullName
= tempPathStr
;
173 HeaderLocationCache
[cacheKey
]=fullName
;
179 // Complain if the file cannot be found and matches the complain
181 if(fullName
.empty() &&
182 this->IncludeRegexComplain
.find(current
.FileName
.c_str()))
184 cmSystemTools::Error("Cannot find file \"",
185 current
.FileName
.c_str(), "\".");
189 // Scan the file if it was found and has not been scanned already.
190 if(!fullName
.empty() && (scanned
.find(fullName
) == scanned
.end()))
192 // Record scanned files.
193 scanned
.insert(fullName
);
195 // Check whether this file is already in the cache
196 std::map
<cmStdString
, cmIncludeLines
*>::iterator fileIt
=
197 this->FileCache
.find(fullName
);
198 if (fileIt
!=this->FileCache
.end())
200 fileIt
->second
->Used
=true;
201 dependencies
.insert(fullName
);
202 for (std::vector
<UnscannedEntry
>::const_iterator incIt
=
203 fileIt
->second
->UnscannedEntries
.begin();
204 incIt
!=fileIt
->second
->UnscannedEntries
.end(); ++incIt
)
206 if (this->Encountered
.find(incIt
->FileName
) ==
207 this->Encountered
.end())
209 this->Encountered
.insert(incIt
->FileName
);
210 this->Unscanned
.push(*incIt
);
217 // Try to scan the file. Just leave it out if we cannot find
219 std::ifstream
fin(fullName
.c_str());
222 // Add this file as a dependency.
223 dependencies
.insert(fullName
);
225 // Scan this file for new dependencies. Pass the directory
226 // containing the file to handle double-quote includes.
227 std::string dir
= cmSystemTools::GetFilenamePath(fullName
);
228 this->Scan(fin
, dir
.c_str(), fullName
);
236 // Write the dependencies to the output stream. Makefile rules
237 // written by the original local generator for this directory
238 // convert the dependencies to paths relative to the home output
239 // directory. We must do the same here.
240 internalDepends
<< obj
<< std::endl
;
241 for(std::set
<cmStdString
>::iterator i
=dependencies
.begin();
242 i
!= dependencies
.end(); ++i
)
244 makeDepends
<< obj
<< ": " <<
245 this->LocalGenerator
->Convert(i
->c_str(),
246 cmLocalGenerator::HOME_OUTPUT
,
247 cmLocalGenerator::MAKEFILE
)
249 internalDepends
<< " " << i
->c_str() << std::endl
;
251 makeDepends
<< std::endl
;
256 //----------------------------------------------------------------------------
257 void cmDependsC::ReadCacheFile()
259 if(this->CacheFileName
.size() == 0)
263 std::ifstream
fin(this->CacheFileName
.c_str());
270 cmIncludeLines
* cacheEntry
=0;
271 bool haveFileName
=false;
273 while(cmSystemTools::GetLineFromStream(fin
, line
))
281 //the first line after an empty line is the name of the parsed file
282 if (haveFileName
==false)
286 cmFileTimeComparison comp
;
287 bool res
=comp
.FileTimeCompare(this->CacheFileName
.c_str(),
288 line
.c_str(), &newer
);
290 if ((res
==true) && (newer
==1)) //cache is newer than the parsed file
292 cacheEntry
=new cmIncludeLines
;
293 this->FileCache
[line
]=cacheEntry
;
295 // file doesn't exist, check that the regular expressions
299 if (line
.find(INCLUDE_REGEX_LINE_MARKER
) == 0)
301 if (line
!= this->IncludeRegexLineString
)
306 else if (line
.find(INCLUDE_REGEX_SCAN_MARKER
) == 0)
308 if (line
!= this->IncludeRegexScanString
)
313 else if (line
.find(INCLUDE_REGEX_COMPLAIN_MARKER
) == 0)
315 if (line
!= this->IncludeRegexComplainString
)
322 else if (cacheEntry
!=0)
324 UnscannedEntry entry
;
325 entry
.FileName
= line
;
326 if (cmSystemTools::GetLineFromStream(fin
, line
))
330 entry
.QuotedLocation
=line
;
332 cacheEntry
->UnscannedEntries
.push_back(entry
);
338 //----------------------------------------------------------------------------
339 void cmDependsC::WriteCacheFile() const
341 if(this->CacheFileName
.size() == 0)
345 std::ofstream
cacheOut(this->CacheFileName
.c_str());
351 cacheOut
<< this->IncludeRegexLineString
<< "\n\n";
352 cacheOut
<< this->IncludeRegexScanString
<< "\n\n";
353 cacheOut
<< this->IncludeRegexComplainString
<< "\n\n";
355 for (std::map
<cmStdString
, cmIncludeLines
*>::const_iterator fileIt
=
356 this->FileCache
.begin();
357 fileIt
!=this->FileCache
.end(); ++fileIt
)
359 if (fileIt
->second
->Used
)
361 cacheOut
<<fileIt
->first
.c_str()<<std::endl
;
363 for (std::vector
<UnscannedEntry
>::const_iterator
364 incIt
=fileIt
->second
->UnscannedEntries
.begin();
365 incIt
!=fileIt
->second
->UnscannedEntries
.end(); ++incIt
)
367 cacheOut
<<incIt
->FileName
.c_str()<<std::endl
;
368 if (incIt
->QuotedLocation
.empty())
370 cacheOut
<<"-"<<std::endl
;
374 cacheOut
<<incIt
->QuotedLocation
.c_str()<<std::endl
;
382 //----------------------------------------------------------------------------
383 void cmDependsC::Scan(std::istream
& is
, const char* directory
,
384 const cmStdString
& fullName
)
386 cmIncludeLines
* newCacheEntry
=new cmIncludeLines
;
387 newCacheEntry
->Used
=true;
388 this->FileCache
[fullName
]=newCacheEntry
;
390 // Read one line at a time.
392 while(cmSystemTools::GetLineFromStream(is
, line
))
394 // Match include directives.
395 if(this->IncludeRegexLine
.find(line
.c_str()))
397 // Get the file being included.
398 UnscannedEntry entry
;
399 entry
.FileName
= this->IncludeRegexLine
.match(2);
400 if(this->IncludeRegexLine
.match(3) == "\"" &&
401 !cmSystemTools::FileIsFullPath(entry
.FileName
.c_str()))
403 // This was a double-quoted include with a relative path. We
404 // must check for the file in the directory containing the
405 // file we are scanning.
406 entry
.QuotedLocation
= directory
;
407 entry
.QuotedLocation
+= "/";
408 entry
.QuotedLocation
+= entry
.FileName
;
411 // Queue the file if it has not yet been encountered and it
412 // matches the regular expression for recursive scanning. Note
413 // that this check does not account for the possibility of two
414 // headers with the same name in different directories when one
415 // is included by double-quotes and the other by angle brackets.
416 // This kind of problem will be fixed when a more
417 // preprocessor-like implementation of this scanner is created.
418 if (this->IncludeRegexScan
.find(entry
.FileName
.c_str()))
420 newCacheEntry
->UnscannedEntries
.push_back(entry
);
421 if(this->Encountered
.find(entry
.FileName
) == this->Encountered
.end())
423 this->Encountered
.insert(entry
.FileName
);
424 this->Unscanned
.push(entry
);