Coverage Report

Created: 2026-03-12 06:35

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