cmTargetSourcesCommand.cxx 11 KB

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