cmTargetSourcesCommand.cxx 11 KB

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