mirror of
https://gitlab.kitware.com/cmake/cmake.git
synced 2026-01-30 04:44:26 +00:00
Change the interface of the `export` command so that `PACKAGE_INFO` and `SBOM` are proper, first-level sub-commands, rather than being implemented via `export(EXPORT ... (PACKAGE_INFO|SBOM) ...)`. This brings consistency with the `install` command and also simplifies argument parsing. Update the documentation and test cases accordingly. Some of the argument handling test cases are now rather gratuitous, and are removed, which also improves consistency with the `install` tests.
710 lines
23 KiB
C++
710 lines
23 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file LICENSE.rst or https://cmake.org/licensing for details. */
|
|
#include "cmExportCommand.h"
|
|
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include <cm/memory>
|
|
#include <cm/optional>
|
|
#include <cmext/algorithm>
|
|
#include <cmext/string_view>
|
|
|
|
#include "cmsys/RegularExpression.hxx"
|
|
|
|
#include "cmArgumentParser.h"
|
|
#include "cmArgumentParserTypes.h"
|
|
#include "cmCryptoHash.h"
|
|
#include "cmExecutionStatus.h"
|
|
#include "cmExperimental.h"
|
|
#include "cmExportBuildAndroidMKGenerator.h"
|
|
#include "cmExportBuildCMakeConfigGenerator.h"
|
|
#include "cmExportBuildFileGenerator.h"
|
|
#include "cmExportBuildPackageInfoGenerator.h"
|
|
#include "cmExportBuildSbomGenerator.h"
|
|
#include "cmExportSet.h"
|
|
#include "cmGeneratedFileStream.h"
|
|
#include "cmGlobalGenerator.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmMessageType.h"
|
|
#include "cmPackageInfoArguments.h"
|
|
#include "cmPolicies.h"
|
|
#include "cmRange.h"
|
|
#include "cmSbomArguments.h"
|
|
#include "cmStateTypes.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSubcommandTable.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmTarget.h"
|
|
#include "cmValue.h"
|
|
|
|
#if defined(__HAIKU__)
|
|
# include <FindDirectory.h>
|
|
# include <StorageDefs.h>
|
|
#endif
|
|
|
|
#if defined(_WIN32) && !defined(__CYGWIN__)
|
|
# include <windows.h>
|
|
#endif
|
|
|
|
static void StorePackageRegistry(cmMakefile& mf, std::string const& package,
|
|
char const* content, char const* hash);
|
|
|
|
static cm::optional<cmExportSet*> GetExportSet(std::string const& name,
|
|
cmGlobalGenerator* generator,
|
|
cmExecutionStatus& status)
|
|
{
|
|
cmExportSetMap& setMap = generator->GetExportSets();
|
|
auto const it = setMap.find(name);
|
|
if (it == setMap.end()) {
|
|
status.SetError(cmStrCat("Export set \""_s, name, "\" not found."_s));
|
|
return cm::nullopt;
|
|
}
|
|
return &it->second;
|
|
}
|
|
|
|
static void AddExportGenerator(
|
|
cmMakefile& makefile, cmGlobalGenerator* globalGenerator,
|
|
std::unique_ptr<cmExportBuildFileGenerator> exportGenerator,
|
|
std::string const& fileName, cmExportSet* exportSet,
|
|
std::string const& cxxModulesDirectory)
|
|
{
|
|
exportGenerator->SetExportFile(fileName.c_str());
|
|
exportGenerator->SetCxxModuleDirectory(cxxModulesDirectory);
|
|
if (exportSet) {
|
|
exportGenerator->SetExportSet(exportSet);
|
|
}
|
|
std::vector<std::string> configurationTypes =
|
|
makefile.GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
|
|
|
|
for (std::string const& ct : configurationTypes) {
|
|
exportGenerator->AddConfiguration(ct);
|
|
}
|
|
if (exportSet) {
|
|
globalGenerator->AddBuildExportExportSet(exportGenerator.get());
|
|
}
|
|
|
|
makefile.AddExportBuildFileGenerator(std::move(exportGenerator));
|
|
}
|
|
|
|
static bool HandleTargetsMode(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
struct Arguments
|
|
{
|
|
cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Targets;
|
|
ArgumentParser::NonEmpty<std::string> ExportSetName;
|
|
ArgumentParser::NonEmpty<std::string> Namespace;
|
|
ArgumentParser::NonEmpty<std::string> Filename;
|
|
ArgumentParser::NonEmpty<std::string> CxxModulesDirectory;
|
|
ArgumentParser::NonEmpty<std::string> AndroidMKFile;
|
|
|
|
bool Append = false;
|
|
bool ExportOld = false;
|
|
};
|
|
|
|
auto parser =
|
|
cmArgumentParser<Arguments>{}
|
|
.Bind("NAMESPACE"_s, &Arguments::Namespace)
|
|
.Bind("FILE"_s, &Arguments::Filename)
|
|
.Bind("CXX_MODULES_DIRECTORY"_s, &Arguments::CxxModulesDirectory)
|
|
.Bind("TARGETS"_s, &Arguments::Targets)
|
|
.Bind("APPEND"_s, &Arguments::Append)
|
|
.Bind("ANDROID_MK"_s, &Arguments::AndroidMKFile)
|
|
.Bind("EXPORT_LINK_INTERFACE_LIBRARIES"_s, &Arguments::ExportOld);
|
|
|
|
std::vector<std::string> unknownArgs;
|
|
Arguments arguments = parser.Parse(args, &unknownArgs);
|
|
|
|
if (!unknownArgs.empty()) {
|
|
status.SetError(
|
|
cmStrCat("Unknown argument: \"", unknownArgs.front(), "\"."));
|
|
return false;
|
|
}
|
|
|
|
std::string fname;
|
|
bool android = false;
|
|
if (!arguments.AndroidMKFile.empty()) {
|
|
fname = arguments.AndroidMKFile;
|
|
android = true;
|
|
} else if (arguments.Filename.empty()) {
|
|
fname = arguments.ExportSetName + ".cmake";
|
|
} else {
|
|
// Make sure the file has a .cmake extension.
|
|
if (cmSystemTools::GetFilenameLastExtension(arguments.Filename) !=
|
|
".cmake") {
|
|
std::ostringstream e;
|
|
e << "FILE option given filename \"" << arguments.Filename
|
|
<< "\" which does not have an extension of \".cmake\".\n";
|
|
status.SetError(e.str());
|
|
return false;
|
|
}
|
|
fname = arguments.Filename;
|
|
}
|
|
|
|
cmMakefile& mf = status.GetMakefile();
|
|
|
|
// Get the file to write.
|
|
if (cmSystemTools::FileIsFullPath(fname)) {
|
|
if (!mf.CanIWriteThisFile(fname)) {
|
|
std::ostringstream e;
|
|
e << "FILE option given filename \"" << fname
|
|
<< "\" which is in the source tree.\n";
|
|
status.SetError(e.str());
|
|
return false;
|
|
}
|
|
} else {
|
|
// Interpret relative paths with respect to the current build dir.
|
|
std::string const& dir = mf.GetCurrentBinaryDirectory();
|
|
fname = cmStrCat(dir, '/', fname);
|
|
}
|
|
|
|
std::vector<cmExportBuildFileGenerator::TargetExport> targets;
|
|
cmGlobalGenerator* gg = mf.GetGlobalGenerator();
|
|
|
|
for (std::string const& currentTarget : *arguments.Targets) {
|
|
if (mf.IsAlias(currentTarget)) {
|
|
std::ostringstream e;
|
|
e << "given ALIAS target \"" << currentTarget
|
|
<< "\" which may not be exported.";
|
|
status.SetError(e.str());
|
|
return false;
|
|
}
|
|
|
|
if (cmTarget* target = gg->FindTarget(currentTarget)) {
|
|
if (target->GetType() == cmStateEnums::UTILITY) {
|
|
status.SetError("given custom target \"" + currentTarget +
|
|
"\" which may not be exported.");
|
|
return false;
|
|
}
|
|
} else {
|
|
std::ostringstream e;
|
|
e << "given target \"" << currentTarget
|
|
<< "\" which is not built by this project.";
|
|
status.SetError(e.str());
|
|
return false;
|
|
}
|
|
targets.emplace_back(currentTarget, std::string{});
|
|
}
|
|
if (arguments.Append) {
|
|
if (cmExportBuildFileGenerator* ebfg = gg->GetExportedTargetsFile(fname)) {
|
|
ebfg->AppendTargets(targets);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// if cmExportBuildFileGenerator is already defined for the file
|
|
// and APPEND is not specified, if CMP0103 is OLD ignore previous definition
|
|
// else raise an error
|
|
if (gg->GetExportedTargetsFile(fname)) {
|
|
switch (mf.GetPolicyStatus(cmPolicies::CMP0103)) {
|
|
case cmPolicies::WARN:
|
|
mf.IssueMessage(
|
|
MessageType::AUTHOR_WARNING,
|
|
cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0103),
|
|
"\n"
|
|
"export() command already specified for the file\n ",
|
|
arguments.Filename, "\nDid you miss 'APPEND' keyword?"));
|
|
CM_FALLTHROUGH;
|
|
case cmPolicies::OLD:
|
|
break;
|
|
default:
|
|
status.SetError(cmStrCat("command already specified for the file\n ",
|
|
arguments.Filename,
|
|
"\nDid you miss 'APPEND' keyword?"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<cmExportBuildFileGenerator> ebfg = nullptr;
|
|
if (android) {
|
|
auto ebag = cm::make_unique<cmExportBuildAndroidMKGenerator>();
|
|
ebag->SetNamespace(arguments.Namespace);
|
|
ebag->SetAppendMode(arguments.Append);
|
|
ebfg = std::move(ebag);
|
|
} else {
|
|
auto ebcg = cm::make_unique<cmExportBuildCMakeConfigGenerator>();
|
|
ebcg->SetNamespace(arguments.Namespace);
|
|
ebcg->SetAppendMode(arguments.Append);
|
|
ebcg->SetExportOld(arguments.ExportOld);
|
|
ebfg = std::move(ebcg);
|
|
}
|
|
|
|
ebfg->SetExportFile(fname.c_str());
|
|
ebfg->SetCxxModuleDirectory(arguments.CxxModulesDirectory);
|
|
ebfg->SetTargets(targets);
|
|
std::vector<std::string> configurationTypes =
|
|
mf.GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
|
|
|
|
for (std::string const& ct : configurationTypes) {
|
|
ebfg->AddConfiguration(ct);
|
|
}
|
|
gg->AddBuildExportSet(ebfg.get());
|
|
mf.AddExportBuildFileGenerator(std::move(ebfg));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool HandleExportMode(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
struct ExportArguments : public ArgumentParser::ParseResult
|
|
{
|
|
ArgumentParser::NonEmpty<std::string> ExportSetName;
|
|
ArgumentParser::MaybeEmpty<std::string> Namespace;
|
|
ArgumentParser::NonEmpty<std::string> Filename;
|
|
ArgumentParser::NonEmpty<std::string> CxxModulesDirectory;
|
|
bool ExportPackageDependencies = false;
|
|
};
|
|
|
|
auto parser =
|
|
cmArgumentParser<ExportArguments>{}
|
|
.Bind("EXPORT"_s, &ExportArguments::ExportSetName)
|
|
.Bind("NAMESPACE"_s, &ExportArguments::Namespace)
|
|
.Bind("FILE"_s, &ExportArguments::Filename)
|
|
.Bind("CXX_MODULES_DIRECTORY"_s, &ExportArguments::CxxModulesDirectory);
|
|
|
|
if (cmExperimental::HasSupportEnabled(
|
|
status.GetMakefile(),
|
|
cmExperimental::Feature::ExportPackageDependencies)) {
|
|
parser.Bind("EXPORT_PACKAGE_DEPENDENCIES"_s,
|
|
&ExportArguments::ExportPackageDependencies);
|
|
}
|
|
|
|
std::vector<std::string> unknownArgs;
|
|
ExportArguments arguments = parser.Parse(args, &unknownArgs);
|
|
|
|
cmMakefile& mf = status.GetMakefile();
|
|
cmGlobalGenerator* gg = mf.GetGlobalGenerator();
|
|
|
|
if (!arguments.Check(args[0], &unknownArgs, status)) {
|
|
cmPolicies::PolicyStatus const p =
|
|
status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0208);
|
|
if (!unknownArgs.empty() || p == cmPolicies::NEW) {
|
|
return false;
|
|
}
|
|
if (p == cmPolicies::WARN) {
|
|
status.GetMakefile().IssueMessage(
|
|
MessageType::AUTHOR_WARNING, cmStrCat("export "_s, status.GetError()));
|
|
status.GetMakefile().IssueMessage(
|
|
MessageType::AUTHOR_WARNING,
|
|
cmPolicies::GetPolicyWarning(cmPolicies::CMP0208));
|
|
}
|
|
}
|
|
|
|
std::string fname;
|
|
if (arguments.Filename.empty()) {
|
|
fname = arguments.ExportSetName + ".cmake";
|
|
} else {
|
|
if (cmSystemTools::GetFilenameLastExtension(arguments.Filename) !=
|
|
".cmake") {
|
|
std::ostringstream e;
|
|
e << "FILE option given filename \"" << arguments.Filename
|
|
<< "\" which does not have an extension of \".cmake\".\n";
|
|
status.SetError(e.str());
|
|
return false;
|
|
}
|
|
fname = arguments.Filename;
|
|
}
|
|
|
|
if (cmSystemTools::FileIsFullPath(fname)) {
|
|
if (!mf.CanIWriteThisFile(fname)) {
|
|
std::ostringstream e;
|
|
e << "FILE option given filename \"" << fname
|
|
<< "\" which is in the source tree.\n";
|
|
status.SetError(e.str());
|
|
return false;
|
|
}
|
|
} else {
|
|
// Interpret relative paths with respect to the current build dir.
|
|
std::string const& dir = mf.GetCurrentBinaryDirectory();
|
|
fname = cmStrCat(dir, '/', fname);
|
|
}
|
|
|
|
cm::optional<cmExportSet*> const exportSet =
|
|
GetExportSet(arguments.ExportSetName, gg, status);
|
|
if (!exportSet) {
|
|
return false;
|
|
}
|
|
|
|
// Set up export file generation.
|
|
auto ebcg = cm::make_unique<cmExportBuildCMakeConfigGenerator>();
|
|
ebcg->SetNamespace(arguments.Namespace);
|
|
ebcg->SetExportPackageDependencies(arguments.ExportPackageDependencies);
|
|
|
|
AddExportGenerator(mf, gg, std::move(ebcg), fname, *exportSet,
|
|
arguments.CxxModulesDirectory);
|
|
return true;
|
|
}
|
|
|
|
template <typename ArgumentsType, typename GeneratorType>
|
|
static bool HandleSpecialExportMode(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
struct ExportArguments
|
|
: public ArgumentsType
|
|
, public ArgumentParser::ParseResult
|
|
{
|
|
ArgumentParser::NonEmpty<std::string> ExportSetName;
|
|
ArgumentParser::NonEmpty<std::string> CxxModulesDirectory;
|
|
|
|
using ArgumentsType::Check;
|
|
using ArgumentParser::ParseResult::Check;
|
|
};
|
|
|
|
auto parser =
|
|
cmArgumentParser<ExportArguments>{}
|
|
.Bind("EXPORT"_s, &ExportArguments::ExportSetName)
|
|
.Bind("CXX_MODULES_DIRECTORY"_s, &ExportArguments::CxxModulesDirectory);
|
|
ArgumentsType::Bind(parser);
|
|
|
|
std::vector<std::string> unknownArgs;
|
|
ExportArguments arguments = parser.Parse(args, &unknownArgs);
|
|
|
|
if (!arguments.Check(args[0], &unknownArgs, status)) {
|
|
return false;
|
|
}
|
|
|
|
if (arguments.ExportSetName.empty()) {
|
|
status.SetError(cmStrCat(args[0], " missing EXPORT."));
|
|
return false;
|
|
}
|
|
|
|
if (!arguments.Check(status) || !arguments.SetMetadataFromProject(status)) {
|
|
return false;
|
|
}
|
|
|
|
cmMakefile& mf = status.GetMakefile();
|
|
cmGlobalGenerator* gg = mf.GetGlobalGenerator();
|
|
|
|
std::string const& dir = mf.GetCurrentBinaryDirectory();
|
|
std::string const fname = cmStrCat(dir, '/', arguments.GetPackageFileName());
|
|
|
|
if (gg->GetExportedTargetsFile(fname)) {
|
|
status.SetError(cmStrCat("command already specified for the file "_s,
|
|
cmSystemTools::GetFilenameName(fname), '.'));
|
|
return false;
|
|
}
|
|
|
|
// Look up the export set
|
|
cm::optional<cmExportSet*> const exportSet =
|
|
GetExportSet(arguments.ExportSetName, gg, status);
|
|
if (!exportSet) {
|
|
return false;
|
|
}
|
|
|
|
// Create the export build generator
|
|
auto ebpg = cm::make_unique<GeneratorType>(arguments);
|
|
AddExportGenerator(mf, gg, std::move(ebpg), fname, *exportSet,
|
|
arguments.CxxModulesDirectory);
|
|
return true;
|
|
}
|
|
|
|
static bool HandlePackageInfoMode(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
if (!cmExperimental::HasSupportEnabled(
|
|
status.GetMakefile(), cmExperimental::Feature::ExportPackageInfo)) {
|
|
status.SetError("does not recognize sub-command PACKAGE_INFO");
|
|
return false;
|
|
}
|
|
|
|
using arg_t = cmPackageInfoArguments;
|
|
using gen_t = cmExportBuildPackageInfoGenerator;
|
|
return HandleSpecialExportMode<arg_t, gen_t>(args, status);
|
|
}
|
|
|
|
static bool HandleSbomMode(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
if (!cmExperimental::HasSupportEnabled(
|
|
status.GetMakefile(), cmExperimental::Feature::GenerateSbom)) {
|
|
status.SetError("does not recognize sub-command SBOM");
|
|
return false;
|
|
}
|
|
|
|
using arg_t = cmSbomArguments;
|
|
using gen_t = cmExportBuildSbomGenerator;
|
|
return HandleSpecialExportMode<arg_t, gen_t>(args, status);
|
|
}
|
|
|
|
static bool HandleSetupMode(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
struct SetupArguments
|
|
{
|
|
ArgumentParser::NonEmpty<std::string> ExportSetName;
|
|
ArgumentParser::NonEmpty<std::string> CxxModulesDirectory;
|
|
std::vector<std::vector<std::string>> PackageDependencyArgs;
|
|
std::vector<std::vector<std::string>> TargetArgs;
|
|
};
|
|
|
|
auto parser = cmArgumentParser<SetupArguments>{};
|
|
parser.Bind("SETUP"_s, &SetupArguments::ExportSetName);
|
|
if (cmExperimental::HasSupportEnabled(
|
|
status.GetMakefile(),
|
|
cmExperimental::Feature::ExportPackageDependencies)) {
|
|
parser.Bind("PACKAGE_DEPENDENCY"_s,
|
|
&SetupArguments::PackageDependencyArgs);
|
|
}
|
|
parser.Bind("TARGET"_s, &SetupArguments::TargetArgs);
|
|
|
|
std::vector<std::string> unknownArgs;
|
|
SetupArguments arguments = parser.Parse(args, &unknownArgs);
|
|
|
|
if (!unknownArgs.empty()) {
|
|
status.SetError("SETUP given unknown argument: \"" + unknownArgs.front() +
|
|
"\".");
|
|
return false;
|
|
}
|
|
|
|
cmMakefile& mf = status.GetMakefile();
|
|
cmGlobalGenerator* gg = mf.GetGlobalGenerator();
|
|
|
|
cmExportSetMap& setMap = gg->GetExportSets();
|
|
auto& exportSet = setMap[arguments.ExportSetName];
|
|
|
|
struct PackageDependencyArguments
|
|
{
|
|
std::string Enabled;
|
|
ArgumentParser::MaybeEmpty<std::vector<std::string>> ExtraArgs;
|
|
};
|
|
|
|
auto packageDependencyParser =
|
|
cmArgumentParser<PackageDependencyArguments>{}
|
|
.Bind("ENABLED"_s, &PackageDependencyArguments::Enabled)
|
|
.Bind("EXTRA_ARGS"_s, &PackageDependencyArguments::ExtraArgs);
|
|
|
|
for (auto const& packageDependencyArgs : arguments.PackageDependencyArgs) {
|
|
if (packageDependencyArgs.empty()) {
|
|
continue;
|
|
}
|
|
PackageDependencyArguments const packageDependencyArguments =
|
|
packageDependencyParser.Parse(
|
|
cmMakeRange(packageDependencyArgs).advance(1), &unknownArgs);
|
|
|
|
if (!unknownArgs.empty()) {
|
|
status.SetError(cmStrCat("PACKAGE_DEPENDENCY given unknown argument: \"",
|
|
unknownArgs.front(), "\"."));
|
|
return false;
|
|
}
|
|
auto& packageDependency =
|
|
exportSet.GetPackageDependencyForSetup(packageDependencyArgs.front());
|
|
if (!packageDependencyArguments.Enabled.empty()) {
|
|
if (packageDependencyArguments.Enabled == "AUTO") {
|
|
packageDependency.Enabled =
|
|
cmExportSet::PackageDependencyExportEnabled::Auto;
|
|
} else if (cmIsOff(packageDependencyArguments.Enabled)) {
|
|
packageDependency.Enabled =
|
|
cmExportSet::PackageDependencyExportEnabled::Off;
|
|
} else if (cmIsOn(packageDependencyArguments.Enabled)) {
|
|
packageDependency.Enabled =
|
|
cmExportSet::PackageDependencyExportEnabled::On;
|
|
} else {
|
|
status.SetError(
|
|
cmStrCat("Invalid enable setting for package dependency: \"",
|
|
packageDependencyArguments.Enabled, '"'));
|
|
return false;
|
|
}
|
|
}
|
|
cm::append(packageDependency.ExtraArguments,
|
|
packageDependencyArguments.ExtraArgs);
|
|
}
|
|
|
|
struct TargetArguments
|
|
{
|
|
std::string XcFrameworkLocation;
|
|
};
|
|
|
|
auto targetParser = cmArgumentParser<TargetArguments>{}.Bind(
|
|
"XCFRAMEWORK_LOCATION"_s, &TargetArguments::XcFrameworkLocation);
|
|
|
|
for (auto const& targetArgs : arguments.TargetArgs) {
|
|
if (targetArgs.empty()) {
|
|
continue;
|
|
}
|
|
TargetArguments const targetArguments =
|
|
targetParser.Parse(cmMakeRange(targetArgs).advance(1), &unknownArgs);
|
|
|
|
if (!unknownArgs.empty()) {
|
|
status.SetError(cmStrCat("TARGET given unknown argument: \"",
|
|
unknownArgs.front(), "\"."));
|
|
return false;
|
|
}
|
|
exportSet.SetXcFrameworkLocation(targetArgs.front(),
|
|
targetArguments.XcFrameworkLocation);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool HandlePackageMode(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
// Parse PACKAGE mode arguments.
|
|
enum Doing
|
|
{
|
|
DoingNone,
|
|
DoingPackage
|
|
};
|
|
Doing doing = DoingPackage;
|
|
std::string package;
|
|
for (unsigned int i = 1; i < args.size(); ++i) {
|
|
if (doing == DoingPackage) {
|
|
package = args[i];
|
|
doing = DoingNone;
|
|
} else {
|
|
std::ostringstream e;
|
|
e << "PACKAGE given unknown argument: " << args[i];
|
|
status.SetError(e.str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Verify the package name.
|
|
if (package.empty()) {
|
|
status.SetError("PACKAGE must be given a package name.");
|
|
return false;
|
|
}
|
|
char const* packageExpr = "^[A-Za-z0-9_.-]+$";
|
|
cmsys::RegularExpression packageRegex(packageExpr);
|
|
if (!packageRegex.find(package)) {
|
|
std::ostringstream e;
|
|
e << "PACKAGE given invalid package name \"" << package << "\". "
|
|
<< "Package names must match \"" << packageExpr << "\".";
|
|
status.SetError(e.str());
|
|
return false;
|
|
}
|
|
|
|
cmMakefile& mf = status.GetMakefile();
|
|
|
|
// CMP0090 decides both the default and what variable changes it.
|
|
switch (mf.GetPolicyStatus(cmPolicies::CMP0090)) {
|
|
case cmPolicies::WARN:
|
|
CM_FALLTHROUGH;
|
|
case cmPolicies::OLD:
|
|
// Default is to export, but can be disabled.
|
|
if (mf.IsOn("CMAKE_EXPORT_NO_PACKAGE_REGISTRY")) {
|
|
return true;
|
|
}
|
|
break;
|
|
case cmPolicies::NEW:
|
|
// Default is to not export, but can be enabled.
|
|
if (!mf.IsOn("CMAKE_EXPORT_PACKAGE_REGISTRY")) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// We store the current build directory in the registry as a value
|
|
// named by a hash of its own content. This is deterministic and is
|
|
// unique with high probability.
|
|
std::string const& outDir = mf.GetCurrentBinaryDirectory();
|
|
cmCryptoHash hasher(cmCryptoHash::AlgoMD5);
|
|
std::string hash = hasher.HashString(outDir);
|
|
StorePackageRegistry(mf, package, outDir.c_str(), hash.c_str());
|
|
|
|
return true;
|
|
}
|
|
|
|
#if defined(_WIN32) && !defined(__CYGWIN__)
|
|
|
|
static void ReportRegistryError(cmMakefile& mf, std::string const& msg,
|
|
std::string const& key, long err)
|
|
{
|
|
std::ostringstream e;
|
|
e << msg << "\n"
|
|
<< " HKEY_CURRENT_USER\\" << key << "\n";
|
|
wchar_t winmsg[1024];
|
|
if (FormatMessageW(
|
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, err,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), winmsg, 1024, 0) > 0) {
|
|
e << "Windows reported:\n"
|
|
<< " " << cmsys::Encoding::ToNarrow(winmsg);
|
|
}
|
|
mf.IssueMessage(MessageType::WARNING, e.str());
|
|
}
|
|
|
|
static void StorePackageRegistry(cmMakefile& mf, std::string const& package,
|
|
char const* content, char const* hash)
|
|
{
|
|
std::string key = cmStrCat("Software\\Kitware\\CMake\\Packages\\", package);
|
|
HKEY hKey;
|
|
LONG err =
|
|
RegCreateKeyExW(HKEY_CURRENT_USER, cmsys::Encoding::ToWide(key).c_str(), 0,
|
|
0, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, 0, &hKey, 0);
|
|
if (err != ERROR_SUCCESS) {
|
|
ReportRegistryError(mf, "Cannot create/open registry key", key, err);
|
|
return;
|
|
}
|
|
|
|
std::wstring wcontent = cmsys::Encoding::ToWide(content);
|
|
err =
|
|
RegSetValueExW(hKey, cmsys::Encoding::ToWide(hash).c_str(), 0, REG_SZ,
|
|
(BYTE const*)wcontent.c_str(),
|
|
static_cast<DWORD>(wcontent.size() + 1) * sizeof(wchar_t));
|
|
RegCloseKey(hKey);
|
|
if (err != ERROR_SUCCESS) {
|
|
std::ostringstream msg;
|
|
msg << "Cannot set registry value \"" << hash << "\" under key";
|
|
ReportRegistryError(mf, msg.str(), key, err);
|
|
return;
|
|
}
|
|
}
|
|
#else
|
|
static void StorePackageRegistry(cmMakefile& mf, std::string const& package,
|
|
char const* content, char const* hash)
|
|
{
|
|
# if defined(__HAIKU__)
|
|
char dir[B_PATH_NAME_LENGTH];
|
|
if (find_directory(B_USER_SETTINGS_DIRECTORY, -1, false, dir, sizeof(dir)) !=
|
|
B_OK) {
|
|
return;
|
|
}
|
|
std::string fname = cmStrCat(dir, "/cmake/packages/", package);
|
|
# else
|
|
std::string fname;
|
|
if (!cmSystemTools::GetEnv("HOME", fname)) {
|
|
return;
|
|
}
|
|
cmSystemTools::ConvertToUnixSlashes(fname);
|
|
fname += "/.cmake/packages/";
|
|
fname += package;
|
|
# endif
|
|
cmSystemTools::MakeDirectory(fname);
|
|
fname += "/";
|
|
fname += hash;
|
|
if (!cmSystemTools::FileExists(fname)) {
|
|
cmGeneratedFileStream entry(fname, true);
|
|
if (entry) {
|
|
entry << content << "\n";
|
|
} else {
|
|
mf.IssueMessage(MessageType::WARNING,
|
|
cmStrCat("Cannot create package registry file:\n"
|
|
" ",
|
|
fname, '\n',
|
|
cmSystemTools::GetLastSystemError(), '\n'));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool cmExportCommand(std::vector<std::string> const& args,
|
|
cmExecutionStatus& status)
|
|
{
|
|
if (args.empty()) {
|
|
return true;
|
|
}
|
|
|
|
static cmSubcommandTable const subcommand{
|
|
{ "TARGETS"_s, HandleTargetsMode },
|
|
{ "EXPORT"_s, HandleExportMode },
|
|
{ "SETUP"_s, HandleSetupMode },
|
|
{ "PACKAGE"_s, HandlePackageMode },
|
|
{ "PACKAGE_INFO"_s, HandlePackageInfoMode },
|
|
{ "SBOM"_s, HandleSbomMode },
|
|
};
|
|
|
|
return subcommand(args[0], args, status);
|
|
}
|