cmTargetSourcesCommand.cxx 10 KB

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