cmExportCommand.cxx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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 "cmExportCommand.h"
  4. #include "cm_static_string_view.hxx"
  5. #include "cmsys/RegularExpression.hxx"
  6. #include <map>
  7. #include <sstream>
  8. #include <utility>
  9. #include "cmAlgorithms.h"
  10. #include "cmArgumentParser.h"
  11. #include "cmExportBuildAndroidMKGenerator.h"
  12. #include "cmExportBuildFileGenerator.h"
  13. #include "cmExportSet.h"
  14. #include "cmGeneratedFileStream.h"
  15. #include "cmGlobalGenerator.h"
  16. #include "cmMakefile.h"
  17. #include "cmMessageType.h"
  18. #include "cmPolicies.h"
  19. #include "cmStateTypes.h"
  20. #include "cmSystemTools.h"
  21. #include "cmTarget.h"
  22. class cmExecutionStatus;
  23. #if defined(__HAIKU__)
  24. # include <FindDirectory.h>
  25. # include <StorageDefs.h>
  26. #endif
  27. bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
  28. cmExecutionStatus&)
  29. {
  30. if (args.size() < 2) {
  31. this->SetError("called with too few arguments");
  32. return false;
  33. }
  34. if (args[0] == "PACKAGE") {
  35. return this->HandlePackage(args);
  36. }
  37. struct Arguments
  38. {
  39. std::string ExportSetName;
  40. std::vector<std::string> Targets;
  41. std::string Namespace;
  42. std::string Filename;
  43. std::string AndroidMKFile;
  44. bool Append = false;
  45. bool ExportOld = false;
  46. };
  47. auto parser = cmArgumentParser<Arguments>{}
  48. .Bind("NAMESPACE"_s, &Arguments::Namespace)
  49. .Bind("FILE"_s, &Arguments::Filename);
  50. if (args[0] == "EXPORT") {
  51. parser.Bind("EXPORT"_s, &Arguments::ExportSetName);
  52. } else {
  53. parser.Bind("TARGETS"_s, &Arguments::Targets);
  54. parser.Bind("ANDROID_MK"_s, &Arguments::AndroidMKFile);
  55. parser.Bind("APPEND"_s, &Arguments::Append);
  56. parser.Bind("EXPORT_LINK_INTERFACE_LIBRARIES"_s, &Arguments::ExportOld);
  57. }
  58. std::vector<std::string> unknownArgs;
  59. std::vector<std::string> keywordsMissingValue;
  60. Arguments const arguments =
  61. parser.Parse(args, &unknownArgs, &keywordsMissingValue);
  62. if (!unknownArgs.empty()) {
  63. this->SetError("Unknown argument: \"" + unknownArgs.front() + "\".");
  64. return false;
  65. }
  66. std::string fname;
  67. bool android = false;
  68. if (!arguments.AndroidMKFile.empty()) {
  69. fname = arguments.AndroidMKFile;
  70. android = true;
  71. }
  72. if (arguments.Filename.empty() && fname.empty()) {
  73. if (args[0] != "EXPORT") {
  74. this->SetError("FILE <filename> option missing.");
  75. return false;
  76. }
  77. fname = arguments.ExportSetName + ".cmake";
  78. } else if (fname.empty()) {
  79. // Make sure the file has a .cmake extension.
  80. if (cmSystemTools::GetFilenameLastExtension(arguments.Filename) !=
  81. ".cmake") {
  82. std::ostringstream e;
  83. e << "FILE option given filename \"" << arguments.Filename
  84. << "\" which does not have an extension of \".cmake\".\n";
  85. this->SetError(e.str());
  86. return false;
  87. }
  88. fname = arguments.Filename;
  89. }
  90. // Get the file to write.
  91. if (cmSystemTools::FileIsFullPath(fname)) {
  92. if (!this->Makefile->CanIWriteThisFile(fname)) {
  93. std::ostringstream e;
  94. e << "FILE option given filename \"" << fname
  95. << "\" which is in the source tree.\n";
  96. this->SetError(e.str());
  97. return false;
  98. }
  99. } else {
  100. // Interpret relative paths with respect to the current build dir.
  101. std::string dir = this->Makefile->GetCurrentBinaryDirectory();
  102. fname = dir + "/" + fname;
  103. }
  104. std::vector<std::string> targets;
  105. cmGlobalGenerator* gg = this->Makefile->GetGlobalGenerator();
  106. cmExportSet* exportSet = nullptr;
  107. if (args[0] == "EXPORT") {
  108. cmExportSetMap& setMap = gg->GetExportSets();
  109. auto const it = setMap.find(arguments.ExportSetName);
  110. if (it == setMap.end()) {
  111. std::ostringstream e;
  112. e << "Export set \"" << arguments.ExportSetName << "\" not found.";
  113. this->SetError(e.str());
  114. return false;
  115. }
  116. exportSet = &it->second;
  117. } else if (!arguments.Targets.empty() ||
  118. cmContains(keywordsMissingValue, "TARGETS")) {
  119. for (std::string const& currentTarget : arguments.Targets) {
  120. if (this->Makefile->IsAlias(currentTarget)) {
  121. std::ostringstream e;
  122. e << "given ALIAS target \"" << currentTarget
  123. << "\" which may not be exported.";
  124. this->SetError(e.str());
  125. return false;
  126. }
  127. if (cmTarget* target = gg->FindTarget(currentTarget)) {
  128. if (target->GetType() == cmStateEnums::UTILITY) {
  129. this->SetError("given custom target \"" + currentTarget +
  130. "\" which may not be exported.");
  131. return false;
  132. }
  133. } else {
  134. std::ostringstream e;
  135. e << "given target \"" << currentTarget
  136. << "\" which is not built by this project.";
  137. this->SetError(e.str());
  138. return false;
  139. }
  140. targets.push_back(currentTarget);
  141. }
  142. if (arguments.Append) {
  143. if (cmExportBuildFileGenerator* ebfg =
  144. gg->GetExportedTargetsFile(fname)) {
  145. ebfg->AppendTargets(targets);
  146. return true;
  147. }
  148. }
  149. } else {
  150. this->SetError("EXPORT or TARGETS specifier missing.");
  151. return false;
  152. }
  153. // Setup export file generation.
  154. cmExportBuildFileGenerator* ebfg = nullptr;
  155. if (android) {
  156. ebfg = new cmExportBuildAndroidMKGenerator;
  157. } else {
  158. ebfg = new cmExportBuildFileGenerator;
  159. }
  160. ebfg->SetExportFile(fname.c_str());
  161. ebfg->SetNamespace(arguments.Namespace);
  162. ebfg->SetAppendMode(arguments.Append);
  163. if (exportSet != nullptr) {
  164. ebfg->SetExportSet(exportSet);
  165. } else {
  166. ebfg->SetTargets(targets);
  167. }
  168. this->Makefile->AddExportBuildFileGenerator(ebfg);
  169. ebfg->SetExportOld(arguments.ExportOld);
  170. // Compute the set of configurations exported.
  171. std::vector<std::string> configurationTypes;
  172. this->Makefile->GetConfigurations(configurationTypes);
  173. if (configurationTypes.empty()) {
  174. configurationTypes.emplace_back();
  175. }
  176. for (std::string const& ct : configurationTypes) {
  177. ebfg->AddConfiguration(ct);
  178. }
  179. if (exportSet != nullptr) {
  180. gg->AddBuildExportExportSet(ebfg);
  181. } else {
  182. gg->AddBuildExportSet(ebfg);
  183. }
  184. return true;
  185. }
  186. bool cmExportCommand::HandlePackage(std::vector<std::string> const& args)
  187. {
  188. // Parse PACKAGE mode arguments.
  189. enum Doing
  190. {
  191. DoingNone,
  192. DoingPackage
  193. };
  194. Doing doing = DoingPackage;
  195. std::string package;
  196. for (unsigned int i = 1; i < args.size(); ++i) {
  197. if (doing == DoingPackage) {
  198. package = args[i];
  199. doing = DoingNone;
  200. } else {
  201. std::ostringstream e;
  202. e << "PACKAGE given unknown argument: " << args[i];
  203. this->SetError(e.str());
  204. return false;
  205. }
  206. }
  207. // Verify the package name.
  208. if (package.empty()) {
  209. this->SetError("PACKAGE must be given a package name.");
  210. return false;
  211. }
  212. const char* packageExpr = "^[A-Za-z0-9_.-]+$";
  213. cmsys::RegularExpression packageRegex(packageExpr);
  214. if (!packageRegex.find(package)) {
  215. std::ostringstream e;
  216. e << "PACKAGE given invalid package name \"" << package << "\". "
  217. << "Package names must match \"" << packageExpr << "\".";
  218. this->SetError(e.str());
  219. return false;
  220. }
  221. // CMP0090 decides both the default and what variable changes it.
  222. switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0090)) {
  223. case cmPolicies::WARN:
  224. case cmPolicies::OLD:
  225. // Default is to export, but can be disabled.
  226. if (this->Makefile->IsOn("CMAKE_EXPORT_NO_PACKAGE_REGISTRY")) {
  227. return true;
  228. }
  229. break;
  230. case cmPolicies::REQUIRED_IF_USED:
  231. case cmPolicies::REQUIRED_ALWAYS:
  232. case cmPolicies::NEW:
  233. // Default is to not export, but can be enabled.
  234. if (!this->Makefile->IsOn("CMAKE_EXPORT_PACKAGE_REGISTRY")) {
  235. return true;
  236. }
  237. break;
  238. }
  239. // We store the current build directory in the registry as a value
  240. // named by a hash of its own content. This is deterministic and is
  241. // unique with high probability.
  242. const std::string& outDir = this->Makefile->GetCurrentBinaryDirectory();
  243. std::string hash = cmSystemTools::ComputeStringMD5(outDir);
  244. #if defined(_WIN32) && !defined(__CYGWIN__)
  245. this->StorePackageRegistryWin(package, outDir.c_str(), hash.c_str());
  246. #else
  247. this->StorePackageRegistryDir(package, outDir.c_str(), hash.c_str());
  248. #endif
  249. return true;
  250. }
  251. #if defined(_WIN32) && !defined(__CYGWIN__)
  252. # include <windows.h>
  253. void cmExportCommand::ReportRegistryError(std::string const& msg,
  254. std::string const& key, long err)
  255. {
  256. std::ostringstream e;
  257. e << msg << "\n"
  258. << " HKEY_CURRENT_USER\\" << key << "\n";
  259. wchar_t winmsg[1024];
  260. if (FormatMessageW(
  261. FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, err,
  262. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), winmsg, 1024, 0) > 0) {
  263. e << "Windows reported:\n"
  264. << " " << cmsys::Encoding::ToNarrow(winmsg);
  265. }
  266. this->Makefile->IssueMessage(MessageType::WARNING, e.str());
  267. }
  268. void cmExportCommand::StorePackageRegistryWin(std::string const& package,
  269. const char* content,
  270. const char* hash)
  271. {
  272. std::string key = cmStrCat("Software\\Kitware\\CMake\\Packages\\", package);
  273. HKEY hKey;
  274. LONG err =
  275. RegCreateKeyExW(HKEY_CURRENT_USER, cmsys::Encoding::ToWide(key).c_str(), 0,
  276. 0, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, 0, &hKey, 0);
  277. if (err != ERROR_SUCCESS) {
  278. this->ReportRegistryError("Cannot create/open registry key", key, err);
  279. return;
  280. }
  281. std::wstring wcontent = cmsys::Encoding::ToWide(content);
  282. err =
  283. RegSetValueExW(hKey, cmsys::Encoding::ToWide(hash).c_str(), 0, REG_SZ,
  284. (BYTE const*)wcontent.c_str(),
  285. static_cast<DWORD>(wcontent.size() + 1) * sizeof(wchar_t));
  286. RegCloseKey(hKey);
  287. if (err != ERROR_SUCCESS) {
  288. std::ostringstream msg;
  289. msg << "Cannot set registry value \"" << hash << "\" under key";
  290. this->ReportRegistryError(msg.str(), key, err);
  291. return;
  292. }
  293. }
  294. #else
  295. void cmExportCommand::StorePackageRegistryDir(std::string const& package,
  296. const char* content,
  297. const char* hash)
  298. {
  299. # if defined(__HAIKU__)
  300. char dir[B_PATH_NAME_LENGTH];
  301. if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, dir, sizeof(dir)) !=
  302. B_OK) {
  303. return;
  304. }
  305. std::string fname = cmStrCat(dir, "/cmake/packages/", package);
  306. # else
  307. std::string fname;
  308. if (!cmSystemTools::GetEnv("HOME", fname)) {
  309. return;
  310. }
  311. cmSystemTools::ConvertToUnixSlashes(fname);
  312. fname += "/.cmake/packages/";
  313. fname += package;
  314. # endif
  315. cmSystemTools::MakeDirectory(fname);
  316. fname += "/";
  317. fname += hash;
  318. if (!cmSystemTools::FileExists(fname)) {
  319. cmGeneratedFileStream entry(fname, true);
  320. if (entry) {
  321. entry << content << "\n";
  322. } else {
  323. std::ostringstream e;
  324. /* clang-format off */
  325. e << "Cannot create package registry file:\n"
  326. << " " << fname << "\n"
  327. << cmSystemTools::GetLastSystemError() << "\n";
  328. /* clang-format on */
  329. this->Makefile->IssueMessage(MessageType::WARNING, e.str());
  330. }
  331. }
  332. }
  333. #endif