resolve cyclic dependency with zstd
[platform/upstream/cmake.git] / Source / cmTargetSourcesCommand.cxx
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 "cmTargetSourcesCommand.h"
4
5 #include <sstream>
6 #include <utility>
7
8 #include <cm/string_view>
9 #include <cmext/string_view>
10
11 #include "cmArgumentParser.h"
12 #include "cmArgumentParserTypes.h"
13 #include "cmExperimental.h"
14 #include "cmFileSet.h"
15 #include "cmGeneratorExpression.h"
16 #include "cmListFileCache.h"
17 #include "cmMakefile.h"
18 #include "cmMessageType.h"
19 #include "cmPolicies.h"
20 #include "cmStateTypes.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
23 #include "cmTarget.h"
24 #include "cmTargetPropCommandBase.h"
25
26 namespace {
27
28 struct FileSetArgs
29 {
30   std::string Type;
31   std::string FileSet;
32   ArgumentParser::MaybeEmpty<std::vector<std::string>> BaseDirs;
33   ArgumentParser::MaybeEmpty<std::vector<std::string>> Files;
34 };
35
36 auto const FileSetArgsParser = cmArgumentParser<FileSetArgs>()
37                                  .Bind("TYPE"_s, &FileSetArgs::Type)
38                                  .Bind("FILE_SET"_s, &FileSetArgs::FileSet)
39                                  .Bind("BASE_DIRS"_s, &FileSetArgs::BaseDirs)
40                                  .Bind("FILES"_s, &FileSetArgs::Files);
41
42 struct FileSetsArgs
43 {
44   std::vector<std::vector<std::string>> FileSets;
45 };
46
47 auto const FileSetsArgsParser =
48   cmArgumentParser<FileSetsArgs>().Bind("FILE_SET"_s, &FileSetsArgs::FileSets);
49
50 class TargetSourcesImpl : public cmTargetPropCommandBase
51 {
52 public:
53   using cmTargetPropCommandBase::cmTargetPropCommandBase;
54
55 protected:
56   void HandleInterfaceContent(cmTarget* tgt,
57                               const std::vector<std::string>& content,
58                               bool prepend, bool system) override
59   {
60     this->cmTargetPropCommandBase::HandleInterfaceContent(
61       tgt,
62       this->ConvertToAbsoluteContent(tgt, content, IsInterface::Yes,
63                                      CheckCMP0076::Yes),
64       prepend, system);
65   }
66
67 private:
68   void HandleMissingTarget(const std::string& name) override
69   {
70     this->Makefile->IssueMessage(
71       MessageType::FATAL_ERROR,
72       cmStrCat("Cannot specify sources for target \"", name,
73                "\" which is not built by this project."));
74   }
75
76   bool HandleDirectContent(cmTarget* tgt,
77                            const std::vector<std::string>& content,
78                            bool /*prepend*/, bool /*system*/) override
79   {
80     tgt->AppendProperty("SOURCES",
81                         this->Join(this->ConvertToAbsoluteContent(
82                           tgt, content, IsInterface::No, CheckCMP0076::Yes)),
83                         this->Makefile->GetBacktrace());
84     return true; // Successfully handled.
85   }
86
87   bool PopulateTargetProperties(const std::string& scope,
88                                 const std::vector<std::string>& content,
89                                 bool prepend, bool system) override
90   {
91     if (!content.empty() && content.front() == "FILE_SET"_s) {
92       return this->HandleFileSetMode(scope, content);
93     }
94     return this->cmTargetPropCommandBase::PopulateTargetProperties(
95       scope, content, prepend, system);
96   }
97
98   std::string Join(const std::vector<std::string>& content) override
99   {
100     return cmJoin(content, ";");
101   }
102
103   enum class IsInterface
104   {
105     Yes,
106     No,
107   };
108   enum class CheckCMP0076
109   {
110     Yes,
111     No,
112   };
113   std::vector<std::string> ConvertToAbsoluteContent(
114     cmTarget* tgt, const std::vector<std::string>& content,
115     IsInterface isInterfaceContent, CheckCMP0076 checkCmp0076);
116
117   bool HandleFileSetMode(const std::string& scope,
118                          const std::vector<std::string>& content);
119   bool HandleOneFileSet(const std::string& scope,
120                         const std::vector<std::string>& content);
121 };
122
123 std::vector<std::string> TargetSourcesImpl::ConvertToAbsoluteContent(
124   cmTarget* tgt, const std::vector<std::string>& content,
125   IsInterface isInterfaceContent, CheckCMP0076 checkCmp0076)
126 {
127   // Skip conversion in case old behavior has been explicitly requested
128   if (checkCmp0076 == CheckCMP0076::Yes &&
129       this->Makefile->GetPolicyStatus(cmPolicies::CMP0076) ==
130         cmPolicies::OLD) {
131     return content;
132   }
133
134   bool changedPath = false;
135   std::vector<std::string> absoluteContent;
136   absoluteContent.reserve(content.size());
137   for (std::string const& src : content) {
138     std::string absoluteSrc;
139     if (cmSystemTools::FileIsFullPath(src) ||
140         cmGeneratorExpression::Find(src) == 0 ||
141         (isInterfaceContent == IsInterface::No &&
142          (this->Makefile->GetCurrentSourceDirectory() ==
143           tgt->GetMakefile()->GetCurrentSourceDirectory()))) {
144       absoluteSrc = src;
145     } else {
146       changedPath = true;
147       absoluteSrc =
148         cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', src);
149     }
150     absoluteContent.push_back(absoluteSrc);
151   }
152
153   if (!changedPath) {
154     return content;
155   }
156
157   bool issueMessage = true;
158   bool useAbsoluteContent = false;
159   std::ostringstream e;
160   if (checkCmp0076 == CheckCMP0076::Yes) {
161     switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0076)) {
162       case cmPolicies::WARN:
163         e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0076) << "\n";
164         break;
165       case cmPolicies::OLD:
166         issueMessage = false;
167         break;
168       case cmPolicies::REQUIRED_ALWAYS:
169       case cmPolicies::REQUIRED_IF_USED:
170         this->Makefile->IssueMessage(
171           MessageType::FATAL_ERROR,
172           cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0076));
173         break;
174       case cmPolicies::NEW: {
175         issueMessage = false;
176         useAbsoluteContent = true;
177         break;
178       }
179     }
180   } else {
181     issueMessage = false;
182     useAbsoluteContent = true;
183   }
184
185   if (issueMessage) {
186     if (isInterfaceContent == IsInterface::Yes) {
187       e << "An interface source of target \"" << tgt->GetName()
188         << "\" has a relative path.";
189     } else {
190       e << "A private source from a directory other than that of target \""
191         << tgt->GetName() << "\" has a relative path.";
192     }
193     this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, e.str());
194   }
195
196   return useAbsoluteContent ? absoluteContent : content;
197 }
198
199 bool TargetSourcesImpl::HandleFileSetMode(
200   const std::string& scope, const std::vector<std::string>& content)
201 {
202   auto args = FileSetsArgsParser.Parse(content, /*unparsedArguments=*/nullptr);
203
204   for (auto& argList : args.FileSets) {
205     argList.emplace(argList.begin(), "FILE_SET"_s);
206     if (!this->HandleOneFileSet(scope, argList)) {
207       return false;
208     }
209   }
210
211   return true;
212 }
213
214 bool TargetSourcesImpl::HandleOneFileSet(
215   const std::string& scope, const std::vector<std::string>& content)
216 {
217   std::vector<std::string> unparsed;
218   auto args = FileSetArgsParser.Parse(content, &unparsed);
219
220   if (!unparsed.empty()) {
221     this->SetError(
222       cmStrCat("Unrecognized keyword: \"", unparsed.front(), "\""));
223     return false;
224   }
225
226   if (args.FileSet.empty()) {
227     this->SetError("FILE_SET must not be empty");
228     return false;
229   }
230
231   if (this->Target->GetType() == cmStateEnums::UTILITY) {
232     this->SetError("FILE_SETs may not be added to custom targets");
233     return false;
234   }
235   if (this->Target->IsFrameworkOnApple()) {
236     this->SetError("FILE_SETs may not be added to FRAMEWORK targets");
237     return false;
238   }
239
240   bool const isDefault = args.Type == args.FileSet ||
241     (args.Type.empty() && args.FileSet[0] >= 'A' && args.FileSet[0] <= 'Z');
242   std::string type = isDefault ? args.FileSet : args.Type;
243
244   cmFileSetVisibility visibility =
245     cmFileSetVisibilityFromName(scope, this->Makefile);
246
247   auto fileSet =
248     this->Target->GetOrCreateFileSet(args.FileSet, type, visibility);
249   if (fileSet.second) {
250     if (!isDefault) {
251       if (!cmFileSet::IsValidName(args.FileSet)) {
252         this->SetError("Non-default file set name must contain only letters, "
253                        "numbers, and underscores, and must not start with a "
254                        "capital letter or underscore");
255         return false;
256       }
257     }
258     if (type.empty()) {
259       this->SetError("Must specify a TYPE when creating file set");
260       return false;
261     }
262     bool const supportCxx20FileSetTypes = cmExperimental::HasSupportEnabled(
263       *this->Makefile, cmExperimental::Feature::CxxModuleCMakeApi);
264
265     if (supportCxx20FileSetTypes) {
266       if (type != "HEADERS"_s && type != "CXX_MODULES"_s &&
267           type != "CXX_MODULE_HEADER_UNITS"_s) {
268         this->SetError(
269           R"(File set TYPE may only be "HEADERS", "CXX_MODULES", or "CXX_MODULE_HEADER_UNITS")");
270         return false;
271       }
272
273       if (cmFileSetVisibilityIsForInterface(visibility) &&
274           !cmFileSetVisibilityIsForSelf(visibility) &&
275           !this->Target->IsImported()) {
276         if (type == "CXX_MODULES"_s || type == "CXX_MODULE_HEADER_UNITS"_s) {
277           this->SetError(
278             R"(File set TYPEs "CXX_MODULES" and "CXX_MODULE_HEADER_UNITS" may not have "INTERFACE" visibility)");
279           return false;
280         }
281       }
282     } else {
283       if (type != "HEADERS"_s) {
284         this->SetError("File set TYPE may only be \"HEADERS\"");
285         return false;
286       }
287     }
288
289     if (args.BaseDirs.empty()) {
290       args.BaseDirs.emplace_back(this->Makefile->GetCurrentSourceDirectory());
291     }
292   } else {
293     type = fileSet.first->GetType();
294     if (!args.Type.empty() && args.Type != type) {
295       this->SetError(cmStrCat(
296         "Type \"", args.Type, "\" for file set \"", fileSet.first->GetName(),
297         "\" does not match original type \"", type, "\""));
298       return false;
299     }
300
301     if (visibility != fileSet.first->GetVisibility()) {
302       this->SetError(
303         cmStrCat("Scope ", scope, " for file set \"", args.FileSet,
304                  "\" does not match original scope ",
305                  cmFileSetVisibilityToName(fileSet.first->GetVisibility())));
306       return false;
307     }
308   }
309
310   auto files = this->Join(this->ConvertToAbsoluteContent(
311     this->Target, args.Files, IsInterface::Yes, CheckCMP0076::No));
312   if (!files.empty()) {
313     fileSet.first->AddFileEntry(
314       BT<std::string>(files, this->Makefile->GetBacktrace()));
315   }
316
317   auto baseDirectories = this->Join(this->ConvertToAbsoluteContent(
318     this->Target, args.BaseDirs, IsInterface::Yes, CheckCMP0076::No));
319   if (!baseDirectories.empty()) {
320     fileSet.first->AddDirectoryEntry(
321       BT<std::string>(baseDirectories, this->Makefile->GetBacktrace()));
322     if (type == "HEADERS"_s || type == "CXX_MODULE_HEADER_UNITS"_s) {
323       for (auto const& dir : cmExpandedList(baseDirectories)) {
324         auto interfaceDirectoriesGenex =
325           cmStrCat("$<BUILD_INTERFACE:", dir, ">");
326         if (cmFileSetVisibilityIsForSelf(visibility)) {
327           this->Target->AppendProperty("INCLUDE_DIRECTORIES",
328                                        interfaceDirectoriesGenex,
329                                        this->Makefile->GetBacktrace());
330         }
331         if (cmFileSetVisibilityIsForInterface(visibility)) {
332           this->Target->AppendProperty("INTERFACE_INCLUDE_DIRECTORIES",
333                                        interfaceDirectoriesGenex,
334                                        this->Makefile->GetBacktrace());
335         }
336       }
337     }
338   }
339
340   return true;
341 }
342
343 } // namespace
344
345 bool cmTargetSourcesCommand(std::vector<std::string> const& args,
346                             cmExecutionStatus& status)
347 {
348   return TargetSourcesImpl(status).HandleArguments(args, "SOURCES");
349 }