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