Coverage Report

Created: 2026-03-12 06:35

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