Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmNinjaUtilityTargetGenerator.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 "cmNinjaUtilityTargetGenerator.h"
4
5
#include <algorithm>
6
#include <array>
7
#include <iterator>
8
#include <memory>
9
#include <set>
10
#include <string>
11
#include <utility>
12
#include <vector>
13
14
#include <cm/optional>
15
16
#include "cmCustomCommand.h"
17
#include "cmCustomCommandGenerator.h"
18
#include "cmGeneratedFileStream.h"
19
#include "cmGeneratorExpression.h"
20
#include "cmGeneratorTarget.h"
21
#include "cmGlobalNinjaGenerator.h"
22
#include "cmLocalNinjaGenerator.h"
23
#include "cmNinjaTypes.h"
24
#include "cmOutputConverter.h"
25
#include "cmSourceFile.h"
26
#include "cmStringAlgorithms.h"
27
#include "cmSystemTools.h"
28
#include "cmTarget.h"
29
#include "cmTargetTypes.h"
30
#include "cmValue.h"
31
32
cmNinjaUtilityTargetGenerator::cmNinjaUtilityTargetGenerator(
33
  cmGeneratorTarget* target)
34
0
  : cmNinjaTargetGenerator(target)
35
0
{
36
0
}
37
38
0
cmNinjaUtilityTargetGenerator::~cmNinjaUtilityTargetGenerator() = default;
39
40
void cmNinjaUtilityTargetGenerator::Generate(std::string const& config)
41
0
{
42
0
  if (!this->GetGeneratorTarget()->Target->IsPerConfig()) {
43
0
    this->WriteUtilBuildStatements(config, config);
44
0
    return;
45
0
  }
46
47
0
  for (auto const& fileConfig : this->GetConfigNames()) {
48
0
    if (!this->GetGlobalGenerator()
49
0
           ->GetCrossConfigs(fileConfig)
50
0
           .count(config)) {
51
0
      continue;
52
0
    }
53
0
    if (fileConfig != config &&
54
0
        this->GetGeneratorTarget()->GetType() ==
55
0
          cm::TargetType::GLOBAL_TARGET) {
56
0
      continue;
57
0
    }
58
0
    this->WriteUtilBuildStatements(config, fileConfig);
59
0
  }
60
0
}
61
62
void cmNinjaUtilityTargetGenerator::WriteUtilBuildStatements(
63
  std::string const& config, std::string const& fileConfig)
64
0
{
65
0
  cmGlobalNinjaGenerator* gg = this->GetGlobalGenerator();
66
0
  cmLocalNinjaGenerator* lg = this->GetLocalGenerator();
67
0
  cmGeneratorTarget* genTarget = this->GetGeneratorTarget();
68
69
0
  std::string configDir;
70
0
  if (genTarget->Target->IsPerConfig()) {
71
0
    configDir = gg->ConfigDirectory(fileConfig);
72
0
  }
73
0
  std::string utilCommandName =
74
0
    cmStrCat(lg->GetCurrentBinaryDirectory(), "/CMakeFiles", configDir, '/',
75
0
             this->GetTargetName(), ".util");
76
0
  utilCommandName = this->ConvertToNinjaPath(utilCommandName);
77
78
0
  cmNinjaBuild phonyBuild("phony");
79
0
  std::vector<std::string> commands;
80
0
  cmNinjaDeps deps;
81
0
  cmGlobalNinjaGenerator::CCOutputs util_outputs(gg);
82
0
  util_outputs.ExplicitOuts.emplace_back(utilCommandName);
83
84
0
  std::string commandDesc;
85
0
  cmGeneratorExpression ge(*this->GetLocalGenerator()->GetCMakeInstance());
86
0
  bool uses_terminal = false;
87
0
  {
88
0
    std::array<std::vector<cmCustomCommand> const*, 2> const cmdLists = {
89
0
      { &genTarget->GetPreBuildCommands(), &genTarget->GetPostBuildCommands() }
90
0
    };
91
92
0
    for (std::vector<cmCustomCommand> const* cmdList : cmdLists) {
93
0
      for (cmCustomCommand const& ci : *cmdList) {
94
0
        cmCustomCommandGenerator ccg(ci, fileConfig, lg);
95
0
        lg->AppendCustomCommandDeps(ccg, deps, fileConfig);
96
0
        lg->AppendCustomCommandLines(ccg, commands);
97
0
        if (ci.GetComment()) {
98
0
          if (!commandDesc.empty()) {
99
0
            commandDesc += "; ";
100
0
          }
101
0
          auto cge = ge.Parse(ci.GetComment().value());
102
0
          commandDesc += cge->Evaluate(this->GetLocalGenerator(), config);
103
0
        }
104
0
        util_outputs.Add(ccg.GetByproducts());
105
0
        if (ci.GetUsesTerminal()) {
106
0
          uses_terminal = true;
107
0
        }
108
0
      }
109
0
    }
110
0
  }
111
112
0
  {
113
0
    std::vector<cmSourceFile*> sources;
114
0
    genTarget->GetSourceFiles(sources, config);
115
0
    for (cmSourceFile const* source : sources) {
116
0
      if (cmCustomCommand const* cc = source->GetCustomCommand()) {
117
0
        cmCustomCommandGenerator ccg(*cc, config, lg);
118
0
        lg->AddCustomCommandTarget(cc, genTarget);
119
120
        // Depend on all custom command outputs.
121
0
        std::vector<std::string> const& ccOutputs = ccg.GetOutputs();
122
0
        std::vector<std::string> const& ccByproducts = ccg.GetByproducts();
123
0
        std::transform(ccOutputs.begin(), ccOutputs.end(),
124
0
                       std::back_inserter(deps), this->MapToNinjaPath());
125
0
        std::transform(ccByproducts.begin(), ccByproducts.end(),
126
0
                       std::back_inserter(deps), this->MapToNinjaPath());
127
0
      }
128
0
    }
129
0
  }
130
131
0
  std::string outputConfig;
132
0
  if (genTarget->Target->IsPerConfig()) {
133
0
    outputConfig = config;
134
0
  }
135
0
  lg->AppendTargetOutputs(genTarget, phonyBuild.Outputs, outputConfig);
136
0
  if (genTarget->Target->GetType() != cm::TargetType::GLOBAL_TARGET) {
137
0
    lg->AppendTargetOutputs(genTarget, gg->GetByproductsForCleanTarget(),
138
0
                            config);
139
0
    std::copy(util_outputs.ExplicitOuts.begin(),
140
0
              util_outputs.ExplicitOuts.end(),
141
0
              std::back_inserter(gg->GetByproductsForCleanTarget()));
142
0
  }
143
0
  lg->AppendTargetDepends(genTarget, deps, config, fileConfig,
144
0
                          DependOnTargetArtifact);
145
146
0
  if (commands.empty()) {
147
0
    phonyBuild.Comment = "Utility command for " + this->GetTargetName();
148
0
    phonyBuild.ExplicitDeps = std::move(deps);
149
0
    if (genTarget->GetType() != cm::TargetType::GLOBAL_TARGET) {
150
0
      gg->WriteBuild(this->GetImplFileStream(fileConfig), phonyBuild);
151
0
    } else {
152
0
      gg->WriteBuild(this->GetCommonFileStream(), phonyBuild);
153
0
    }
154
0
  } else {
155
0
    std::string command = lg->BuildCommandLine(
156
0
      commands, config, fileConfig, "utility", this->GeneratorTarget);
157
0
    std::string desc;
158
0
    cmValue echoStr = genTarget->GetProperty("EchoString");
159
0
    if (echoStr) {
160
0
      desc = *echoStr;
161
0
    } else if (!commandDesc.empty()) {
162
0
      desc = commandDesc;
163
0
    } else {
164
0
      desc = "Running utility command for " + this->GetTargetName();
165
0
    }
166
167
    // TODO: fix problematic global targets.  For now, search and replace the
168
    // makefile vars.
169
0
    cmSystemTools::ReplaceString(
170
0
      command, "$(CMAKE_SOURCE_DIR)",
171
0
      lg->ConvertToOutputFormat(lg->GetSourceDirectory(),
172
0
                                cmOutputConverter::SHELL));
173
0
    cmSystemTools::ReplaceString(
174
0
      command, "$(CMAKE_BINARY_DIR)",
175
0
      lg->ConvertToOutputFormat(lg->GetBinaryDirectory(),
176
0
                                cmOutputConverter::SHELL));
177
0
    cmSystemTools::ReplaceString(command, "$(ARGS)", "");
178
0
    command = gg->ExpandCFGIntDir(command, config);
179
180
0
    std::string ccConfig;
181
0
    if (genTarget->Target->IsPerConfig() &&
182
0
        genTarget->GetType() != cm::TargetType::GLOBAL_TARGET) {
183
0
      ccConfig = config;
184
0
    }
185
0
    if (config == fileConfig ||
186
0
        gg->GetPerConfigUtilityTargets().count(genTarget->GetName())) {
187
0
      gg->WriteCustomCommandBuild(
188
0
        command, desc, "Utility command for " + this->GetTargetName(),
189
0
        /*depfile*/ "", /*job_pool*/ "", uses_terminal,
190
0
        /*restat*/ true, ccConfig, std::move(util_outputs), std::move(deps));
191
0
    }
192
193
0
    phonyBuild.ExplicitDeps.push_back(utilCommandName);
194
0
    if (genTarget->GetType() != cm::TargetType::GLOBAL_TARGET) {
195
0
      gg->WriteBuild(this->GetImplFileStream(fileConfig), phonyBuild);
196
0
    } else {
197
0
      gg->WriteBuild(this->GetCommonFileStream(), phonyBuild);
198
0
    }
199
0
  }
200
201
  // Find ADDITIONAL_CLEAN_FILES
202
0
  this->AdditionalCleanFiles(config);
203
204
  // Add an alias for the logical target name regardless of what directory
205
  // contains it.  Skip this for GLOBAL_TARGET because they are meant to
206
  // be per-directory and have one at the top-level anyway.
207
0
  if (genTarget->GetType() != cm::TargetType::GLOBAL_TARGET) {
208
0
    gg->AddTargetAlias(this->GetTargetName(), genTarget, config);
209
0
  }
210
0
}