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