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