1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 
4 #include "cmDependsCompiler.h"
5 
6 #include <algorithm>
7 #include <map>
8 #include <memory>
9 #include <string>
10 #include <unordered_set>
11 #include <utility>
12 
13 #include <cm/optional>
14 #include <cm/string_view>
15 #include <cm/vector>
16 #include <cmext/string_view>
17 
18 #include "cmsys/FStream.hxx"
19 
20 #include "cmFileTime.h"
21 #include "cmGccDepfileReader.h"
22 #include "cmGccDepfileReaderTypes.h"
23 #include "cmGlobalUnixMakefileGenerator3.h"
24 #include "cmLocalUnixMakefileGenerator3.h"
25 #include "cmStringAlgorithms.h"
26 #include "cmSystemTools.h"
27 
CheckDependencies(const std::string & internalDepFile,const std::vector<std::string> & depFiles,cmDepends::DependencyMap & dependencies,const std::function<bool (const std::string &)> & isValidPath)28 bool cmDependsCompiler::CheckDependencies(
29   const std::string& internalDepFile, const std::vector<std::string>& depFiles,
30   cmDepends::DependencyMap& dependencies,
31   const std::function<bool(const std::string&)>& isValidPath)
32 {
33   bool status = true;
34   bool forceReadDeps = true;
35 
36   cmFileTime internalDepFileTime;
37   // read cached dependencies stored in internal file
38   if (cmSystemTools::FileExists(internalDepFile)) {
39     internalDepFileTime.Load(internalDepFile);
40     forceReadDeps = false;
41 
42     // read current dependencies
43     cmsys::ifstream fin(internalDepFile.c_str());
44     if (fin) {
45       std::string line;
46       std::string depender;
47       std::vector<std::string>* currentDependencies = nullptr;
48       while (std::getline(fin, line)) {
49         if (line.empty() || line.front() == '#') {
50           continue;
51         }
52         // Drop carriage return character at the end
53         if (line.back() == '\r') {
54           line.pop_back();
55           if (line.empty()) {
56             continue;
57           }
58         }
59         // Check if this a depender line
60         if (line.front() != ' ') {
61           depender = std::move(line);
62           currentDependencies = &dependencies[depender];
63           continue;
64         }
65         // This is a dependee line
66         if (currentDependencies != nullptr) {
67           currentDependencies->emplace_back(line.substr(1));
68         }
69       }
70       fin.close();
71     }
72   }
73 
74   // Now, update dependencies map with all new compiler generated
75   // dependencies files
76   cmFileTime depFileTime;
77   for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) {
78     const auto& source = *dep++;
79     const auto& target = *dep++;
80     const auto& format = *dep++;
81     const auto& depFile = *dep;
82 
83     if (!cmSystemTools::FileExists(depFile)) {
84       continue;
85     }
86 
87     if (!forceReadDeps) {
88       depFileTime.Load(depFile);
89     }
90     if (forceReadDeps || depFileTime.Compare(internalDepFileTime) >= 0) {
91       status = false;
92       if (this->Verbose) {
93         cmSystemTools::Stdout(cmStrCat("Dependencies file \"", depFile,
94                                        "\" is newer than depends file \"",
95                                        internalDepFile, "\".\n"));
96       }
97 
98       std::vector<std::string> depends;
99       if (format == "custom"_s) {
100         auto deps = cmReadGccDepfile(
101           depFile.c_str(), this->LocalGenerator->GetCurrentBinaryDirectory());
102         if (!deps) {
103           continue;
104         }
105 
106         for (auto& entry : *deps) {
107           depends = std::move(entry.paths);
108           if (isValidPath) {
109             cm::erase_if(depends, isValidPath);
110           }
111           // copy depends for each target, except first one, which can be
112           // moved
113           for (auto index = entry.rules.size() - 1; index > 0; --index) {
114             dependencies[entry.rules[index]] = depends;
115           }
116           dependencies[entry.rules.front()] = std::move(depends);
117         }
118       } else {
119         if (format == "msvc"_s) {
120           cmsys::ifstream fin(depFile.c_str());
121           if (!fin) {
122             continue;
123           }
124 
125           std::string line;
126           if (!isValidPath) {
127             // insert source as first dependency
128             depends.push_back(source);
129           }
130           while (cmSystemTools::GetLineFromStream(fin, line)) {
131             depends.emplace_back(std::move(line));
132           }
133         } else if (format == "gcc"_s) {
134           auto deps = cmReadGccDepfile(
135             depFile.c_str(), this->LocalGenerator->GetCurrentBinaryDirectory(),
136             GccDepfilePrependPaths::Deps);
137           if (!deps) {
138             continue;
139           }
140 
141           // dependencies generated by the compiler contains only one target
142           depends = std::move(deps->front().paths);
143           if (depends.empty()) {
144             // unexpectedly empty, ignore it and continue
145             continue;
146           }
147 
148           // depending of the effective format of the dependencies file
149           // generated by the compiler, the target can be wrongly identified
150           // as a dependency so remove it from the list
151           if (depends.front() == target) {
152             depends.erase(depends.begin());
153           }
154 
155           // ensure source file is the first dependency
156           if (depends.front() != source) {
157             cm::erase(depends, source);
158             if (!isValidPath) {
159               depends.insert(depends.begin(), source);
160             }
161           } else if (isValidPath) {
162             // remove first dependency because it must not be filtered out
163             depends.erase(depends.begin());
164           }
165         } else {
166           // unknown format, ignore it
167           continue;
168         }
169 
170         if (isValidPath) {
171           cm::erase_if(depends, isValidPath);
172           // insert source as first dependency
173           depends.insert(depends.begin(), source);
174         }
175 
176         dependencies[target] = std::move(depends);
177       }
178     }
179   }
180 
181   return status;
182 }
183 
WriteDependencies(const cmDepends::DependencyMap & dependencies,std::ostream & makeDepends,std::ostream & internalDepends)184 void cmDependsCompiler::WriteDependencies(
185   const cmDepends::DependencyMap& dependencies, std::ostream& makeDepends,
186   std::ostream& internalDepends)
187 {
188   // dependencies file consumed by make tool
189   const auto& lineContinue = static_cast<cmGlobalUnixMakefileGenerator3*>(
190                                this->LocalGenerator->GetGlobalGenerator())
191                                ->LineContinueDirective;
192   bool supportLongLineDepend = static_cast<cmGlobalUnixMakefileGenerator3*>(
193                                  this->LocalGenerator->GetGlobalGenerator())
194                                  ->SupportsLongLineDependencies();
195   cmDepends::DependencyMap makeDependencies(dependencies);
196   std::unordered_set<cm::string_view> phonyTargets;
197 
198   // external dependencies file
199   for (auto& node : makeDependencies) {
200     auto target = this->LocalGenerator->ConvertToMakefilePath(
201       this->LocalGenerator->MaybeRelativeToTopBinDir(node.first));
202     auto& deps = node.second;
203     std::transform(deps.cbegin(), deps.cend(), deps.begin(),
204                    [this](const std::string& dep) {
205                      return this->LocalGenerator->ConvertToMakefilePath(
206                        this->LocalGenerator->MaybeRelativeToTopBinDir(dep));
207                    });
208 
209     bool first_dep = true;
210     if (supportLongLineDepend) {
211       makeDepends << target << ": ";
212     }
213     for (const auto& dep : deps) {
214       if (supportLongLineDepend) {
215         if (first_dep) {
216           first_dep = false;
217           makeDepends << dep;
218         } else {
219           makeDepends << ' ' << lineContinue << "  " << dep;
220         }
221       } else {
222         makeDepends << target << ": " << dep << std::endl;
223       }
224 
225       phonyTargets.emplace(dep.data(), dep.length());
226     }
227     makeDepends << std::endl << std::endl;
228   }
229 
230   // add phony targets
231   for (const auto& target : phonyTargets) {
232     makeDepends << std::endl << target << ':' << std::endl;
233   }
234 
235   // internal dependencies file
236   for (const auto& node : dependencies) {
237     internalDepends << node.first << std::endl;
238     for (const auto& dep : node.second) {
239       internalDepends << ' ' << dep << std::endl;
240     }
241     internalDepends << std::endl;
242   }
243 }
244 
ClearDependencies(const std::vector<std::string> & depFiles)245 void cmDependsCompiler::ClearDependencies(
246   const std::vector<std::string>& depFiles)
247 {
248   for (auto dep = depFiles.begin(); dep != depFiles.end(); dep++) {
249     dep += 3;
250     cmSystemTools::RemoveFile(*dep);
251   }
252 }
253