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