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"
10 #include <cmext/algorithm>
11 #include <cmext/string_view>
13 #include "cmsys/RegularExpression.hxx"
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"
30 #if defined(__HAIKU__)
31 # include <FindDirectory.h>
32 # include <StorageDefs.h>
35 #if defined(_WIN32) && !defined(__CYGWIN__)
39 static bool HandlePackage(std::vector<std::string> const& args,
40 cmExecutionStatus& status);
42 static void StorePackageRegistry(cmMakefile& mf, std::string const& package,
43 const char* content, const char* hash);
45 bool cmExportCommand(std::vector<std::string> const& args,
46 cmExecutionStatus& status)
48 if (args.size() < 2) {
49 status.SetError("called with too few arguments");
53 if (args[0] == "PACKAGE") {
54 return HandlePackage(args, status);
59 std::string ExportSetName;
60 std::vector<std::string> Targets;
61 std::string Namespace;
63 std::string AndroidMKFile;
65 bool ExportOld = false;
68 auto parser = cmArgumentParser<Arguments>{}
69 .Bind("NAMESPACE"_s, &Arguments::Namespace)
70 .Bind("FILE"_s, &Arguments::Filename);
72 if (args[0] == "EXPORT") {
73 parser.Bind("EXPORT"_s, &Arguments::ExportSetName);
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);
81 std::vector<std::string> unknownArgs;
82 std::vector<std::string> keywordsMissingValue;
83 Arguments const arguments =
84 parser.Parse(args, &unknownArgs, &keywordsMissingValue);
86 if (!unknownArgs.empty()) {
87 status.SetError("Unknown argument: \"" + unknownArgs.front() + "\".");
93 if (!arguments.AndroidMKFile.empty()) {
94 fname = arguments.AndroidMKFile;
97 if (arguments.Filename.empty() && fname.empty()) {
98 if (args[0] != "EXPORT") {
99 status.SetError("FILE <filename> option missing.");
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) !=
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());
113 fname = arguments.Filename;
116 cmMakefile& mf = status.GetMakefile();
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());
128 // Interpret relative paths with respect to the current build dir.
129 std::string const& dir = mf.GetCurrentBinaryDirectory();
130 fname = dir + "/" + fname;
133 std::vector<std::string> targets;
135 cmGlobalGenerator* gg = mf.GetGlobalGenerator();
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());
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());
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.");
166 std::ostringstream e;
167 e << "given target \"" << currentTarget
168 << "\" which is not built by this project.";
169 status.SetError(e.str());
172 targets.push_back(currentTarget);
174 if (arguments.Append) {
175 if (cmExportBuildFileGenerator* ebfg =
176 gg->GetExportedTargetsFile(fname)) {
177 ebfg->AppendTargets(targets);
182 status.SetError("EXPORT or TARGETS specifier missing.");
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:
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?"));
198 case cmPolicies::OLD:
201 status.SetError(cmStrCat("command already specified for the file\n ",
203 "\nDid you miss 'APPEND' keyword?"));
208 // Setup export file generation.
209 std::unique_ptr<cmExportBuildFileGenerator> ebfg = nullptr;
211 ebfg = cm::make_unique<cmExportBuildAndroidMKGenerator>();
213 ebfg = cm::make_unique<cmExportBuildFileGenerator>();
215 ebfg->SetExportFile(fname.c_str());
216 ebfg->SetNamespace(arguments.Namespace);
217 ebfg->SetAppendMode(arguments.Append);
218 if (exportSet != nullptr) {
219 ebfg->SetExportSet(exportSet);
221 ebfg->SetTargets(targets);
223 ebfg->SetExportOld(arguments.ExportOld);
225 // Compute the set of configurations exported.
226 std::vector<std::string> configurationTypes =
227 mf.GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
229 for (std::string const& ct : configurationTypes) {
230 ebfg->AddConfiguration(ct);
232 if (exportSet != nullptr) {
233 gg->AddBuildExportExportSet(ebfg.get());
235 gg->AddBuildExportSet(ebfg.get());
237 mf.AddExportBuildFileGenerator(std::move(ebfg));
242 static bool HandlePackage(std::vector<std::string> const& args,
243 cmExecutionStatus& status)
245 // Parse PACKAGE mode arguments.
251 Doing doing = DoingPackage;
253 for (unsigned int i = 1; i < args.size(); ++i) {
254 if (doing == DoingPackage) {
258 std::ostringstream e;
259 e << "PACKAGE given unknown argument: " << args[i];
260 status.SetError(e.str());
265 // Verify the package name.
266 if (package.empty()) {
267 status.SetError("PACKAGE must be given a package name.");
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());
280 cmMakefile& mf = status.GetMakefile();
282 // CMP0090 decides both the default and what variable changes it.
283 switch (mf.GetPolicyStatus(cmPolicies::CMP0090)) {
284 case cmPolicies::WARN:
286 case cmPolicies::OLD:
287 // Default is to export, but can be disabled.
288 if (mf.IsOn("CMAKE_EXPORT_NO_PACKAGE_REGISTRY")) {
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")) {
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());
312 #if defined(_WIN32) && !defined(__CYGWIN__)
314 static void ReportRegistryError(cmMakefile& mf, std::string const& msg,
315 std::string const& key, long err)
317 std::ostringstream e;
319 << " HKEY_CURRENT_USER\\" << key << "\n";
320 wchar_t winmsg[1024];
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);
327 mf.IssueMessage(MessageType::WARNING, e.str());
330 static void StorePackageRegistry(cmMakefile& mf, std::string const& package,
331 const char* content, const char* hash)
333 std::string key = cmStrCat("Software\\Kitware\\CMake\\Packages\\", package);
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);
343 std::wstring wcontent = cmsys::Encoding::ToWide(content);
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));
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);
357 static void StorePackageRegistry(cmMakefile& mf, std::string const& package,
358 const char* content, const char* hash)
360 # if defined(__HAIKU__)
361 char dir[B_PATH_NAME_LENGTH];
362 if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, dir, sizeof(dir)) !=
366 std::string fname = cmStrCat(dir, "/cmake/packages/", package);
369 if (!cmSystemTools::GetEnv("HOME", fname)) {
372 cmSystemTools::ConvertToUnixSlashes(fname);
373 fname += "/.cmake/packages/";
376 cmSystemTools::MakeDirectory(fname);
379 if (!cmSystemTools::FileExists(fname)) {
380 cmGeneratedFileStream entry(fname, true);
382 entry << content << "\n";
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());