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