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 <cm/optional>
11 #include <cmext/string_view>
13 #include "cmsys/RegularExpression.hxx"
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"
32 #if defined(__HAIKU__)
33 # include <FindDirectory.h>
34 # include <StorageDefs.h>
37 #if defined(_WIN32) && !defined(__CYGWIN__)
41 static bool HandlePackage(std::vector<std::string> const& args,
42 cmExecutionStatus& status);
44 static void StorePackageRegistry(cmMakefile& mf, std::string const& package,
45 const char* content, const char* hash);
47 bool cmExportCommand(std::vector<std::string> const& args,
48 cmExecutionStatus& status)
50 if (args.size() < 2) {
51 status.SetError("called with too few arguments");
55 if (args[0] == "PACKAGE") {
56 return HandlePackage(args, status);
61 std::string ExportSetName;
62 cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Targets;
63 std::string Namespace;
65 std::string AndroidMKFile;
66 std::string CxxModulesDirectory;
68 bool ExportOld = false;
71 auto parser = cmArgumentParser<Arguments>{}
72 .Bind("NAMESPACE"_s, &Arguments::Namespace)
73 .Bind("FILE"_s, &Arguments::Filename);
75 bool const supportCxx20FileSetTypes = cmExperimental::HasSupportEnabled(
76 status.GetMakefile(), cmExperimental::Feature::CxxModuleCMakeApi);
77 if (supportCxx20FileSetTypes) {
78 parser.Bind("CXX_MODULES_DIRECTORY"_s, &Arguments::CxxModulesDirectory);
81 if (args[0] == "EXPORT") {
82 parser.Bind("EXPORT"_s, &Arguments::ExportSetName);
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);
90 std::vector<std::string> unknownArgs;
91 Arguments const arguments = parser.Parse(args, &unknownArgs);
93 if (!unknownArgs.empty()) {
94 status.SetError("Unknown argument: \"" + unknownArgs.front() + "\".");
100 if (!arguments.AndroidMKFile.empty()) {
101 fname = arguments.AndroidMKFile;
104 if (arguments.Filename.empty() && fname.empty()) {
105 if (args[0] != "EXPORT") {
106 status.SetError("FILE <filename> option missing.");
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) !=
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());
120 fname = arguments.Filename;
123 cmMakefile& mf = status.GetMakefile();
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());
135 // Interpret relative paths with respect to the current build dir.
136 std::string const& dir = mf.GetCurrentBinaryDirectory();
137 fname = dir + "/" + fname;
140 std::vector<std::string> targets;
142 cmGlobalGenerator* gg = mf.GetGlobalGenerator();
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());
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());
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.");
172 std::ostringstream e;
173 e << "given target \"" << currentTarget
174 << "\" which is not built by this project.";
175 status.SetError(e.str());
178 targets.push_back(currentTarget);
180 if (arguments.Append) {
181 if (cmExportBuildFileGenerator* ebfg =
182 gg->GetExportedTargetsFile(fname)) {
183 ebfg->AppendTargets(targets);
188 status.SetError("EXPORT or TARGETS specifier missing.");
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:
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?"));
204 case cmPolicies::OLD:
207 status.SetError(cmStrCat("command already specified for the file\n ",
209 "\nDid you miss 'APPEND' keyword?"));
214 // Setup export file generation.
215 std::unique_ptr<cmExportBuildFileGenerator> ebfg = nullptr;
217 ebfg = cm::make_unique<cmExportBuildAndroidMKGenerator>();
219 ebfg = cm::make_unique<cmExportBuildFileGenerator>();
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);
228 ebfg->SetTargets(targets);
230 ebfg->SetExportOld(arguments.ExportOld);
232 // Compute the set of configurations exported.
233 std::vector<std::string> configurationTypes =
234 mf.GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
236 for (std::string const& ct : configurationTypes) {
237 ebfg->AddConfiguration(ct);
239 if (exportSet != nullptr) {
240 gg->AddBuildExportExportSet(ebfg.get());
242 gg->AddBuildExportSet(ebfg.get());
244 mf.AddExportBuildFileGenerator(std::move(ebfg));
249 static bool HandlePackage(std::vector<std::string> const& args,
250 cmExecutionStatus& status)
252 // Parse PACKAGE mode arguments.
258 Doing doing = DoingPackage;
260 for (unsigned int i = 1; i < args.size(); ++i) {
261 if (doing == DoingPackage) {
265 std::ostringstream e;
266 e << "PACKAGE given unknown argument: " << args[i];
267 status.SetError(e.str());
272 // Verify the package name.
273 if (package.empty()) {
274 status.SetError("PACKAGE must be given a package name.");
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());
287 cmMakefile& mf = status.GetMakefile();
289 // CMP0090 decides both the default and what variable changes it.
290 switch (mf.GetPolicyStatus(cmPolicies::CMP0090)) {
291 case cmPolicies::WARN:
293 case cmPolicies::OLD:
294 // Default is to export, but can be disabled.
295 if (mf.IsOn("CMAKE_EXPORT_NO_PACKAGE_REGISTRY")) {
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")) {
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());
319 #if defined(_WIN32) && !defined(__CYGWIN__)
321 static void ReportRegistryError(cmMakefile& mf, std::string const& msg,
322 std::string const& key, long err)
324 std::ostringstream e;
326 << " HKEY_CURRENT_USER\\" << key << "\n";
327 wchar_t winmsg[1024];
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);
334 mf.IssueMessage(MessageType::WARNING, e.str());
337 static void StorePackageRegistry(cmMakefile& mf, std::string const& package,
338 const char* content, const char* hash)
340 std::string key = cmStrCat("Software\\Kitware\\CMake\\Packages\\", package);
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);
350 std::wstring wcontent = cmsys::Encoding::ToWide(content);
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));
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);
364 static void StorePackageRegistry(cmMakefile& mf, std::string const& package,
365 const char* content, const char* hash)
367 # if defined(__HAIKU__)
368 char dir[B_PATH_NAME_LENGTH];
369 if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, dir, sizeof(dir)) !=
373 std::string fname = cmStrCat(dir, "/cmake/packages/", package);
376 if (!cmSystemTools::GetEnv("HOME", fname)) {
379 cmSystemTools::ConvertToUnixSlashes(fname);
380 fname += "/.cmake/packages/";
383 cmSystemTools::MakeDirectory(fname);
386 if (!cmSystemTools::FileExists(fname)) {
387 cmGeneratedFileStream entry(fname, true);
389 entry << content << "\n";
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());