Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmTargetTraceDependencies.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
#include "cmTargetTraceDependencies.h"
4
5
#include <utility>
6
7
#include <cmext/algorithm>
8
#include <cmext/string_view>
9
10
#include "cmCustomCommand.h"
11
#include "cmCustomCommandGenerator.h"
12
#include "cmGeneratorTarget.h"
13
#include "cmGlobalGenerator.h"
14
#include "cmList.h"
15
#include "cmListFileCache.h"
16
#include "cmMakefile.h"
17
#include "cmMessageType.h"
18
#include "cmSourceFile.h"
19
#include "cmStringAlgorithms.h"
20
#include "cmSystemTools.h"
21
#include "cmTarget.h"
22
#include "cmTargetTypes.h"
23
#include "cmValue.h"
24
25
cmTargetTraceDependencies::cmTargetTraceDependencies(cmGeneratorTarget* target)
26
0
  : GeneratorTarget(target)
27
0
{
28
  // Convenience.
29
0
  this->Makefile = target->Target->GetMakefile();
30
0
  this->LocalGenerator = target->GetLocalGenerator();
31
0
  this->GlobalGenerator = this->LocalGenerator->GetGlobalGenerator();
32
0
  this->CurrentEntry = nullptr;
33
34
  // Queue all the source files already specified for the target.
35
0
  std::set<cmSourceFile*> emitted;
36
0
  std::vector<std::string> const& configs =
37
0
    this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
38
0
  for (std::string const& c : configs) {
39
0
    std::vector<cmSourceFile*> sources;
40
0
    this->GeneratorTarget->GetSourceFiles(sources, c);
41
0
    for (cmSourceFile* sf : sources) {
42
0
      std::set<cmGeneratorTarget const*> const tgts =
43
0
        this->GlobalGenerator->GetFilenameTargetDepends(sf);
44
0
      if (cm::contains(tgts, this->GeneratorTarget)) {
45
0
        this->GeneratorTarget->LocalGenerator->IssueMessage(
46
0
          MessageType::FATAL_ERROR,
47
0
          cmStrCat("Evaluation output file\n  \"", sf->ResolveFullPath(),
48
0
                   "\"\ndepends on the sources of a target it is used in.  "
49
0
                   "This is a dependency loop and is not allowed."));
50
0
        return;
51
0
      }
52
0
      if (emitted.insert(sf).second && this->SourcesQueued.insert(sf).second) {
53
0
        this->SourceQueue.push(sf);
54
0
      }
55
0
    }
56
0
  }
57
58
  // Queue pre-build, pre-link, and post-build rule dependencies.
59
0
  this->CheckCustomCommands(this->GeneratorTarget->GetPreBuildCommands());
60
0
  this->CheckCustomCommands(this->GeneratorTarget->GetPreLinkCommands());
61
0
  this->CheckCustomCommands(this->GeneratorTarget->GetPostBuildCommands());
62
0
}
63
64
void cmTargetTraceDependencies::Trace()
65
0
{
66
  // Process one dependency at a time until the queue is empty.
67
0
  while (!this->SourceQueue.empty()) {
68
    // Get the next source from the queue.
69
0
    cmSourceFile* sf = this->SourceQueue.front();
70
0
    this->SourceQueue.pop();
71
0
    this->CurrentEntry = &this->GeneratorTarget->SourceDepends[sf];
72
73
    // Queue dependencies added explicitly by the user.
74
0
    if (cmValue additionalDeps = sf->GetProperty("OBJECT_DEPENDS")) {
75
0
      cmList objDeps{ *additionalDeps };
76
0
      for (auto& objDep : objDeps) {
77
0
        if (cmSystemTools::FileIsFullPath(objDep)) {
78
0
          objDep = cmSystemTools::CollapseFullPath(objDep);
79
0
        }
80
0
      }
81
0
      this->FollowNames(objDeps);
82
0
    }
83
84
    // Queue the source needed to generate this file, if any.
85
0
    this->FollowName(sf->ResolveFullPath());
86
87
    // Queue dependencies added programmatically by commands.
88
0
    this->FollowNames(sf->GetDepends());
89
90
    // Queue custom command dependencies.
91
0
    if (cmCustomCommand const* cc = sf->GetCustomCommand()) {
92
0
      this->CheckCustomCommand(*cc);
93
0
    }
94
0
  }
95
0
  this->CurrentEntry = nullptr;
96
97
0
  this->GeneratorTarget->AddTracedSources(this->NewSources);
98
0
}
99
100
void cmTargetTraceDependencies::QueueSource(cmSourceFile* sf)
101
0
{
102
0
  if (this->SourcesQueued.insert(sf).second) {
103
0
    this->SourceQueue.push(sf);
104
105
    // Make sure this file is in the target at the end.
106
0
    this->NewSources.push_back(sf->ResolveFullPath());
107
0
  }
108
0
}
109
110
void cmTargetTraceDependencies::FollowName(std::string const& name)
111
0
{
112
  // Use lower bound with key comparison to not repeat the search for the
113
  // insert position if the name could not be found (which is the common case).
114
0
  auto i = this->NameMap.lower_bound(name);
115
0
  if (i == this->NameMap.end() || i->first != name) {
116
    // Check if we know how to generate this file.
117
0
    cmSourcesWithOutput sources =
118
0
      this->LocalGenerator->GetSourcesWithOutput(name);
119
    // If we failed to find a target or source and we have a relative path, it
120
    // might be a valid source if made relative to the current binary
121
    // directory.
122
0
    if (!sources.Target && !sources.Source &&
123
0
        !cmSystemTools::FileIsFullPath(name)) {
124
0
      auto fullname =
125
0
        cmStrCat(this->Makefile->GetCurrentBinaryDirectory(), '/', name);
126
0
      fullname = cmSystemTools::CollapseFullPath(
127
0
        fullname, this->Makefile->GetHomeOutputDirectory());
128
0
      sources = this->LocalGenerator->GetSourcesWithOutput(fullname);
129
0
    }
130
0
    i = this->NameMap.emplace_hint(i, name, sources);
131
0
  }
132
0
  if (cmTarget* t = i->second.Target) {
133
    // The name is a byproduct of a utility target or a PRE_BUILD, PRE_LINK, or
134
    // POST_BUILD command.
135
0
    this->GeneratorTarget->Target->AddUtility(t->GetName(), false);
136
137
0
    this->GeneratorTarget->Target->AddCodegenDependency(t->GetName());
138
0
  }
139
0
  if (cmSourceFile* sf = i->second.Source) {
140
    // For now only follow the dependency if the source file is not a
141
    // byproduct.  Semantics of byproducts in a non-Ninja context will have to
142
    // be defined first.
143
0
    if (!i->second.SourceIsByproduct) {
144
      // Record the dependency we just followed.
145
0
      if (this->CurrentEntry) {
146
0
        this->CurrentEntry->Depends.push_back(sf);
147
0
      }
148
0
      this->QueueSource(sf);
149
0
    }
150
0
  }
151
0
}
152
153
void cmTargetTraceDependencies::FollowNames(
154
  std::vector<std::string> const& names)
155
0
{
156
0
  for (std::string const& name : names) {
157
0
    this->FollowName(name);
158
0
  }
159
0
}
160
161
bool cmTargetTraceDependencies::IsUtility(std::string const& dep)
162
0
{
163
  // Dependencies on targets (utilities) are supposed to be named by
164
  // just the target name.  However for compatibility we support
165
  // naming the output file generated by the target (assuming there is
166
  // no output-name property which old code would not have set).  In
167
  // that case the target name will be the file basename of the
168
  // dependency.
169
0
  std::string util;
170
0
  if (cmHasSuffix(dep, ".exe"_s)) {
171
0
    util = cmSystemTools::GetFilenameWithoutLastExtension(dep);
172
0
  } else {
173
0
    util = cmSystemTools::GetFilenameName(dep);
174
0
  }
175
176
  // Check for a target with this name.
177
0
  if (cmGeneratorTarget* t =
178
0
        this->GeneratorTarget->GetLocalGenerator()->FindGeneratorTargetToUse(
179
0
          util)) {
180
    // If we find the target and the dep was given as a full path,
181
    // then make sure it was not a full path to something else, and
182
    // the fact that the name matched a target was just a coincidence.
183
0
    if (cmSystemTools::FileIsFullPath(dep)) {
184
0
      if (t->GetType() >= cm::TargetType::EXECUTABLE &&
185
0
          t->GetType() <= cm::TargetType::MODULE_LIBRARY) {
186
        // This is really only for compatibility so we do not need to
187
        // worry about configuration names and output names.
188
0
        std::string tLocation = t->GetLocationForBuild();
189
0
        tLocation = cmSystemTools::GetFilenamePath(tLocation);
190
0
        std::string depLocation = cmSystemTools::GetFilenamePath(dep);
191
0
        depLocation = cmSystemTools::CollapseFullPath(depLocation);
192
0
        tLocation = cmSystemTools::CollapseFullPath(tLocation);
193
0
        if (depLocation == tLocation) {
194
0
          this->GeneratorTarget->Target->AddUtility(util, false);
195
0
          return true;
196
0
        }
197
0
      }
198
0
    } else {
199
      // The original name of the dependency was not a full path.  It
200
      // must name a target, so add the target-level dependency.
201
0
      this->GeneratorTarget->Target->AddUtility(util, true);
202
0
      return true;
203
0
    }
204
0
  }
205
206
  // The dependency does not name a target built in this project.
207
0
  return false;
208
0
}
209
210
void cmTargetTraceDependencies::CheckCustomCommand(cmCustomCommand const& cc)
211
0
{
212
  // Collect dependencies referenced by all configurations.
213
0
  std::set<std::string> depends;
214
0
  for (std::string const& config :
215
0
       this->Makefile->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig)) {
216
0
    for (cmCustomCommandGenerator const& ccg :
217
0
         this->LocalGenerator->MakeCustomCommandGenerators(cc, config)) {
218
      // Collect target-level dependencies referenced in command lines.
219
0
      for (auto const& util : ccg.GetUtilities()) {
220
0
        this->GeneratorTarget->Target->AddUtility(util);
221
222
0
        if (ccg.GetCC().GetCodegen()) {
223
0
          this->GeneratorTarget->Target->AddCodegenDependency(
224
0
            util.Value.first);
225
0
        }
226
0
      }
227
228
      // Collect file-level dependencies referenced in DEPENDS.
229
0
      depends.insert(ccg.GetDepends().begin(), ccg.GetDepends().end());
230
0
    }
231
0
  }
232
233
  // Queue file-level dependencies.
234
0
  for (std::string const& dep : depends) {
235
0
    if (!this->IsUtility(dep)) {
236
      // The dependency does not name a target and may be a file we
237
      // know how to generate.  Queue it.
238
0
      this->FollowName(dep);
239
0
    } else {
240
0
      this->GeneratorTarget->Target->AddCodegenDependency(dep);
241
0
    }
242
0
  }
243
0
}
244
245
void cmTargetTraceDependencies::CheckCustomCommands(
246
  std::vector<cmCustomCommand> const& commands)
247
0
{
248
0
  for (cmCustomCommand const& command : commands) {
249
0
    this->CheckCustomCommand(command);
250
0
  }
251
0
}