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