1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-05-08 22:37:04 +08:00

cmake_host_system_information: query windows registry

Fixes: #21240, #23367
This commit is contained in:
Marc Chevrier 2022-04-01 14:57:12 +02:00 committed by Brad King
parent 591426f5a0
commit 17ff86547e
33 changed files with 1073 additions and 3 deletions

View File

@ -1,9 +1,23 @@
cmake_host_system_information cmake_host_system_information
----------------------------- -----------------------------
Query host system specific information. Query various host system information.
.. code-block:: cmake Synopsis
^^^^^^^^
.. parsed-literal::
`Query host system specific information`_
cmake_host_system_information(RESULT <variable> QUERY <key> ...)
`Query Windows registry`_
cmake_host_system_information(RESULT <variable> QUERY WINDOWS_REGISTRY <key> ...)
Query host system specific information
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
::
cmake_host_system_information(RESULT <variable> QUERY <key> ...) cmake_host_system_information(RESULT <variable> QUERY <key> ...)
@ -180,7 +194,7 @@ distribution-specific files`_ to collect OS identification data and map it
into `man 5 os-release`_ variables. into `man 5 os-release`_ variables.
Fallback Interface Variables Fallback Interface Variables
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ """"""""""""""""""""""""""""
.. variable:: CMAKE_GET_OS_RELEASE_FALLBACK_SCRIPTS .. variable:: CMAKE_GET_OS_RELEASE_FALLBACK_SCRIPTS
@ -246,3 +260,135 @@ Example:
.. _man 5 os-release: https://www.freedesktop.org/software/systemd/man/os-release.html .. _man 5 os-release: https://www.freedesktop.org/software/systemd/man/os-release.html
.. _various distribution-specific files: http://linuxmafia.com/faq/Admin/release-files.html .. _various distribution-specific files: http://linuxmafia.com/faq/Admin/release-files.html
Query Windows registry
^^^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 3.24
::
cmake_host_system_information(RESULT <variable>
QUERY WINDOWS_REGISTRY <key> [VALUE_NAMES|SUBKEYS|VALUE <name>]
[VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[SEPARATOR <separator>]
[ERROR_VARIABLE <result>])
Performs query operations on local computer registry subkey. Returns a list of
subkeys or value names that are located under the specified subkey in the
registry or the data of the specified value name. The result of the queried
entity is stored in ``<variable>``.
.. note::
Querying registry for any other platforms than ``Windows``, including
``CYGWIN``, will always returns an empty string and sets an error message in
the variable specified with sub-option ``ERROR_VARIABLE``.
``<key>`` specify the full path of a subkey on the local computer. The
``<key>`` must include a valid root key. Valid root keys for the local computer
are:
* ``HKLM`` or ``HKEY_LOCAL_MACHINE``
* ``HKCU`` or ``HKEY_CURRENT_USER``
* ``HKCR`` or ``HKEY_CLASSES_ROOT``
* ``HKU`` or ``HKEY_USERS``
* ``HKCC`` or ``HKEY_CURRENT_CONFIG``
And, optionally, the path to a subkey under the specified root key. The path
separator can be the slash or the backslash. ``<key>`` is not case sensitive.
For example:
.. code-block:: cmake
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "HKLM")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "HKLM/SOFTWARE/Kitware")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "HKCU\\SOFTWARE\\Kitware")
``VALUE_NAMES``
Request the list of value names defined under ``<key>``. If a default value
is defined, it will be identified with the special name ``(default)``.
``SUBKEYS``
Request the list of subkeys defined under ``<key>``.
``VALUE <name>``
Request the data stored in value named ``<name>``. If ``VALUE`` is not
specified or argument is the special name ``(default)``, the content of the
default value, if any, will be returned.
.. code-block:: cmake
# query default value for HKLM/SOFTWARE/Kitware key
cmake_host_system_information(RESULT result
QUERY WINDOWS_REGISTRY "HKLM/SOFTWARE/Kitware")
# query default value for HKLM/SOFTWARE/Kitware key using special value name
cmake_host_system_information(RESULT result
QUERY WINDOWS_REGISTRY "HKLM/SOFTWARE/Kitware"
VALUE "(default)")
Supported types are:
* ``REG_SZ``.
* ``REG_EXPAND_SZ``. The returned data is expanded.
* ``REG_MULTI_SZ``. The returned is expressed as a CMake list. See also
``SEPARATOR`` sub-option.
* ``REG_DWORD``.
* ``REG_QWORD``.
For all other types, an empty string is returned.
``VIEW``
Specify which registry views must be queried. When not specified, ``BOTH``
view is used.
``64``
Query the 64bit registry. On ``32bit Windows``, returns always an empty
string.
``32``
Query the 32bit registry.
``64_32``
For ``VALUE`` sub-option or default value, query the registry using view
``64``, and if the request failed, query the registry using view ``32``.
For ``VALUE_NAMES`` and ``SUBKEYS`` sub-options, query both views (``64``
and ``32``) and merge the results (sorted and duplicates removed).
``32_64``
For ``VALUE`` sub-option or default value, query the registry using view
``32``, and if the request failed, query the registry using view ``64``.
For ``VALUE_NAMES`` and ``SUBKEYS`` sub-options, query both views (``32``
and ``64``) and merge the results (sorted and duplicates removed).
``HOST``
Query the registry matching the architecture of the host: ``64`` on ``64bit
Windows`` and ``32`` on ``32bit Windows``.
``TARGET``
Query the registry matching the architecture specified by
:variable:`CMAKE_SIZEOF_VOID_P` variable. If not defined, fallback to
``HOST`` view.
``BOTH``
Query both views (``32`` and ``64``). The order depends of the following
rules: If :variable:`CMAKE_SIZEOF_VOID_P` variable is defined. Use the
following view depending of the content of this variable:
* ``8``: ``64_32``
* ``4``: ``32_64``
If :variable:`CMAKE_SIZEOF_VOID_P` variable is not defined, rely on
architecture of the host:
* ``64bit``: ``64_32``
* ``32bit``: ``32``
``SEPARATOR``
Specify the separator character for ``REG_MULTI_SZ`` type. When not
specified, the character ``\0`` is used.
``ERROR_VARIABLE <result>``
Returns any error raised during query operation. In case of success, the
variable holds an empty string.

View File

@ -0,0 +1,5 @@
chsi-query-windows-registry
---------------------------
* :command:`cmake_host_system_information` command gains the capability, on
``Windows`` platform, to query the registry.

View File

@ -460,6 +460,8 @@ set(SRCS
cmVariableWatch.h cmVariableWatch.h
cmVersion.cxx cmVersion.cxx
cmVersion.h cmVersion.h
cmWindowsRegistry.cxx
cmWindowsRegistry.h
cmWorkerPool.cxx cmWorkerPool.cxx
cmWorkerPool.h cmWorkerPool.h
cmWorkingDirectory.cxx cmWorkingDirectory.cxx

View File

@ -9,6 +9,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <unordered_map>
#include <utility> #include <utility>
#include <cm/optional> #include <cm/optional>
@ -19,10 +20,13 @@
#include "cmsys/Glob.hxx" #include "cmsys/Glob.hxx"
#include "cmsys/SystemInformation.hxx" #include "cmsys/SystemInformation.hxx"
#include "cmArgumentParser.h"
#include "cmExecutionStatus.h" #include "cmExecutionStatus.h"
#include "cmMakefile.h" #include "cmMakefile.h"
#include "cmRange.h"
#include "cmStringAlgorithms.h" #include "cmStringAlgorithms.h"
#include "cmSystemTools.h" #include "cmSystemTools.h"
#include "cmWindowsRegistry.h"
#ifdef _WIN32 #ifdef _WIN32
# include "cmAlgorithms.h" # include "cmAlgorithms.h"
@ -459,6 +463,105 @@ cm::optional<std::string> GetValueChained(GetterFn current, Next... chain)
} }
return GetValueChained(chain...); return GetValueChained(chain...);
} }
template <typename Range>
bool QueryWindowsRegistry(Range args, cmExecutionStatus& status,
std::string const& variable)
{
using View = cmWindowsRegistry::View;
static std::unordered_map<cm::string_view, cmWindowsRegistry::View>
ViewDefinitions{
{ "BOTH"_s, View::Both }, { "HOST"_s, View::Host },
{ "TARGET"_s, View::Target }, { "32"_s, View::Reg32 },
{ "64"_s, View::Reg64 }, { "32_64"_s, View::Reg32_64 },
{ "64_32"_s, View::Reg64_32 }
};
if (args.empty()) {
status.SetError("missing <key> specification.");
return false;
}
std::string const& key = *args.begin();
struct Arguments
{
std::string ValueName;
bool ValueNames = false;
bool SubKeys = false;
std::string View;
std::string Separator;
std::string ErrorVariable;
};
cmArgumentParser<Arguments> parser;
parser.Bind("VALUE"_s, &Arguments::ValueName)
.Bind("VALUE_NAMES"_s, &Arguments::ValueNames)
.Bind("SUBKEYS"_s, &Arguments::SubKeys)
.Bind("VIEW"_s, &Arguments::View)
.Bind("SEPARATOR"_s, &Arguments::Separator)
.Bind("ERROR_VARIABLE"_s, &Arguments::ErrorVariable);
std::vector<std::string> invalidArgs;
std::vector<std::string> keywordsMissingValue;
Arguments const arguments =
parser.Parse(args.advance(1), &invalidArgs, &keywordsMissingValue);
if (!invalidArgs.empty()) {
status.SetError(cmStrCat("given invalid argument(s) \"",
cmJoin(invalidArgs, ", "_s), "\"."));
return false;
}
if (!keywordsMissingValue.empty()) {
status.SetError(cmStrCat("missing expected value for argument(s) \"",
cmJoin(keywordsMissingValue, ", "_s), "\"."));
return false;
}
if ((!arguments.ValueName.empty() &&
(arguments.ValueNames || arguments.SubKeys)) ||
(arguments.ValueName.empty() && arguments.ValueNames &&
arguments.SubKeys)) {
status.SetError("given mutually exclusive sub-options \"VALUE\", "
"\"VALUE_NAMES\" or \"SUBKEYS\".");
return false;
}
if (!arguments.View.empty() &&
ViewDefinitions.find(arguments.View) == ViewDefinitions.end()) {
status.SetError(
cmStrCat("given invalid value for \"VIEW\": ", arguments.View, '.'));
return false;
}
auto& makefile = status.GetMakefile();
makefile.AddDefinition(variable, ""_s);
auto view =
arguments.View.empty() ? View::Both : ViewDefinitions[arguments.View];
cmWindowsRegistry registry(makefile);
if (arguments.ValueNames) {
auto result = registry.GetValueNames(key, view);
if (result) {
makefile.AddDefinition(variable, cmJoin(*result, ";"_s));
}
} else if (arguments.SubKeys) {
auto result = registry.GetSubKeys(key, view);
if (result) {
makefile.AddDefinition(variable, cmJoin(*result, ";"_s));
}
} else {
auto result =
registry.ReadValue(key, arguments.ValueName, view, arguments.Separator);
if (result) {
makefile.AddDefinition(variable, *result);
}
}
// return error message if requested
if (!arguments.ErrorVariable.empty()) {
makefile.AddDefinition(arguments.ErrorVariable, registry.GetLastError());
}
return true;
}
// END Private functions // END Private functions
} // anonymous namespace } // anonymous namespace
@ -481,6 +584,11 @@ bool cmCMakeHostSystemInformationCommand(std::vector<std::string> const& args,
return false; return false;
} }
if (args[current_index + 1] == "WINDOWS_REGISTRY"_s) {
return QueryWindowsRegistry(cmMakeRange(args).advance(current_index + 2),
status, variable);
}
static cmsys::SystemInformation info; static cmsys::SystemInformation info;
static auto initialized = false; static auto initialized = false;
if (!initialized) { if (!initialized) {

View File

@ -0,0 +1,442 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmWindowsRegistry.h"
#if defined(_WIN32) && !defined(__CYGWIN__)
# include <algorithm>
# include <cstdint>
# include <exception>
# include <iterator>
# include <utility>
# include <vector>
# include <cm/memory>
# include <cmext/string_view>
# include <windows.h>
# include "cmsys/Encoding.hxx"
# include "cmsys/SystemTools.hxx"
# include "cmMakefile.h"
# include "cmStringAlgorithms.h"
# include "cmValue.h"
#endif
#if defined(_WIN32) && !defined(__CYGWIN__)
namespace {
bool Is64BitWindows()
{
# if defined(_WIN64)
// 64-bit programs run only on Win64
return true;
# else
// 32-bit programs run on both 32-bit and 64-bit Windows, so we must check.
BOOL isWow64 = false;
return IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64;
# endif
}
// class registry_exception
class registry_error : public std::exception
{
public:
registry_error(std::string msg)
: What(std::move(msg))
{
}
~registry_error() override = default;
const char* what() const noexcept override { return What.c_str(); }
private:
std::string What;
};
// Class KeyHandler
class KeyHandler
{
public:
using View = cmWindowsRegistry::View;
KeyHandler(HKEY hkey)
: Handler(hkey)
{
}
~KeyHandler() { RegCloseKey(this->Handler); }
static KeyHandler OpenKey(cm::string_view key, View view);
std::string ReadValue(cm::string_view name, cm::string_view separator);
std::vector<std::string> GetValueNames();
std::vector<std::string> GetSubKeys();
private:
static std::string FormatSystemError(LSTATUS status);
HKEY Handler;
};
KeyHandler KeyHandler::OpenKey(cm::string_view key, View view)
{
if (view == View::Reg64 && !Is64BitWindows()) {
throw registry_error("No 64bit registry on Windows32.");
}
auto start = key.find_first_of("\\/"_s);
auto rootKey = key.substr(0, start);
HKEY hRootKey;
if (rootKey == "HKCU"_s || rootKey == "HKEY_CURRENT_USER"_s) {
hRootKey = HKEY_CURRENT_USER;
} else if (rootKey == "HKLM"_s || rootKey == "HKEY_LOCAL_MACHINE"_s) {
hRootKey = HKEY_LOCAL_MACHINE;
} else if (rootKey == "HKCR"_s || rootKey == "HKEY_CLASSES_ROOT"_s) {
hRootKey = HKEY_CLASSES_ROOT;
} else if (rootKey == "HKCC"_s || rootKey == "HKEY_CURRENT_CONFIG"_s) {
hRootKey = HKEY_CURRENT_CONFIG;
} else if (rootKey == "HKU"_s || rootKey == "HKEY_USERS"_s) {
hRootKey = HKEY_USERS;
} else {
throw registry_error(cmStrCat(rootKey, ": invalid root key."));
}
std::wstring subKey;
if (start != cm::string_view::npos) {
subKey = cmsys::Encoding::ToWide(key.substr(start + 1).data());
}
// Update path format
std::replace(subKey.begin(), subKey.end(), L'/', L'\\');
REGSAM options = KEY_READ;
if (Is64BitWindows()) {
options |= view == View::Reg64 ? KEY_WOW64_64KEY : KEY_WOW64_32KEY;
}
HKEY hKey;
if (LSTATUS status = RegOpenKeyExW(hRootKey, subKey.c_str(), 0, options,
&hKey) != ERROR_SUCCESS) {
throw registry_error(FormatSystemError(status));
}
return KeyHandler(hKey);
}
std::string KeyHandler::FormatSystemError(LSTATUS status)
{
std::string formattedMessage;
LPWSTR message = nullptr;
DWORD size = 1024;
if (FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr,
status, 0, reinterpret_cast<LPWSTR>(&message), size, nullptr) == 0) {
formattedMessage = "Windows Registry: unexpected error.";
} else {
formattedMessage = cmTrimWhitespace(cmsys::Encoding::ToNarrow(message));
}
LocalFree(message);
return formattedMessage;
}
std::string KeyHandler::ReadValue(cm::string_view name,
cm::string_view separator)
{
LSTATUS status;
DWORD size;
// pick-up maximum size for value
if ((status = RegQueryInfoKeyW(this->Handler, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr,
&size, nullptr, nullptr)) != ERROR_SUCCESS) {
throw registry_error(this->FormatSystemError(status));
}
auto data = cm::make_unique<BYTE[]>(size);
DWORD type;
auto valueName = cmsys::Encoding::ToWide(name.data());
if ((status = RegQueryValueExW(this->Handler, valueName.c_str(), nullptr,
&type, data.get(), &size)) != ERROR_SUCCESS) {
throw registry_error(this->FormatSystemError(status));
}
switch (type) {
case REG_SZ:
return cmsys::Encoding::ToNarrow(reinterpret_cast<wchar_t*>(data.get()));
break;
case REG_EXPAND_SZ: {
auto expandSize = ExpandEnvironmentStringsW(
reinterpret_cast<wchar_t*>(data.get()), nullptr, 0);
auto expandData = cm::make_unique<wchar_t[]>(expandSize + 1);
if (ExpandEnvironmentStringsW(reinterpret_cast<wchar_t*>(data.get()),
expandData.get(), expandSize + 1) == 0) {
throw registry_error(this->FormatSystemError(GetLastError()));
} else {
return cmsys::Encoding::ToNarrow(expandData.get());
}
} break;
case REG_DWORD:
return std::to_string(*reinterpret_cast<std::uint32_t*>(data.get()));
break;
case REG_QWORD:
return std::to_string(*reinterpret_cast<std::uint64_t*>(data.get()));
break;
case REG_MULTI_SZ: {
// replace separator with semicolon
auto sep = cmsys::Encoding::ToWide(separator.data())[0];
std::replace(reinterpret_cast<wchar_t*>(data.get()),
reinterpret_cast<wchar_t*>(data.get()) +
(size / sizeof(wchar_t)) - 1,
sep, L';');
return cmsys::Encoding::ToNarrow(reinterpret_cast<wchar_t*>(data.get()));
} break;
default:
throw registry_error(cmStrCat(type, ": unsupported type."));
}
}
std::vector<std::string> KeyHandler::GetValueNames()
{
LSTATUS status;
DWORD maxSize;
// pick-up maximum size for value names
if ((status = RegQueryInfoKeyW(
this->Handler, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, &maxSize, nullptr, nullptr, nullptr)) != ERROR_SUCCESS) {
throw registry_error(this->FormatSystemError(status));
}
// increment size for final null
auto data = cm::make_unique<wchar_t[]>(++maxSize);
DWORD index = 0;
DWORD size = maxSize;
std::vector<std::string> valueNames;
while ((status = RegEnumValueW(this->Handler, index, data.get(), &size,
nullptr, nullptr, nullptr, nullptr)) ==
ERROR_SUCCESS) {
auto name = cmsys::Encoding::ToNarrow(data.get());
valueNames.push_back(name.empty() ? "(default)" : name);
size = maxSize;
++index;
}
if (status != ERROR_NO_MORE_ITEMS) {
throw registry_error(this->FormatSystemError(status));
}
return valueNames;
}
std::vector<std::string> KeyHandler::GetSubKeys()
{
LSTATUS status;
DWORD size;
// pick-up maximum size for subkeys
if ((status = RegQueryInfoKeyW(
this->Handler, nullptr, nullptr, nullptr, nullptr, &size, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr)) != ERROR_SUCCESS) {
throw registry_error(this->FormatSystemError(status));
}
// increment size for final null
auto data = cm::make_unique<wchar_t[]>(++size);
DWORD index = 0;
std::vector<std::string> subKeys;
while ((status = RegEnumKeyW(this->Handler, index, data.get(), size)) ==
ERROR_SUCCESS) {
subKeys.push_back(cmsys::Encoding::ToNarrow(data.get()));
++index;
}
if (status != ERROR_NO_MORE_ITEMS) {
throw registry_error(this->FormatSystemError(status));
}
return subKeys;
}
}
#endif
// class cmWindowsRegistry
cmWindowsRegistry::cmWindowsRegistry(cmMakefile& makefile)
#if !defined(_WIN32) || defined(__CYGWIN__)
: LastError("No Registry on this platform.")
#endif
{
#if defined(_WIN32) && !defined(__CYGWIN__)
if (cmValue targetSize = makefile.GetDefinition("CMAKE_SIZEOF_VOID_P")) {
this->TargetSize = targetSize == "8" ? 64 : 32;
}
#else
(void)makefile;
#endif
}
cm::string_view cmWindowsRegistry::GetLastError() const
{
return this->LastError;
}
#if defined(_WIN32) && !defined(__CYGWIN__)
std::vector<cmWindowsRegistry::View> cmWindowsRegistry::ComputeViews(View view)
{
switch (view) {
case View::Both:
switch (this->TargetSize) {
case 64:
return std::vector<View>{ View::Reg64, View::Reg32 };
break;
case 32:
return Is64BitWindows()
? std::vector<View>{ View::Reg32, View::Reg64 }
: std::vector<View>{ View::Reg32 };
break;
default:
// No language specified, fallback to host architecture
return Is64BitWindows()
? std::vector<View>{ View::Reg64, View::Reg32 }
: std::vector<View>{ View::Reg32 };
break;
}
break;
case View::Target:
switch (this->TargetSize) {
case 64:
return std::vector<View>{ View::Reg64 };
break;
case 32:
return std::vector<View>{ View::Reg32 };
break;
default:
break;
}
CM_FALLTHROUGH;
case View::Host:
return std::vector<View>{ Is64BitWindows() ? View::Reg64 : View::Reg32 };
break;
case View::Reg64_32:
return Is64BitWindows() ? std::vector<View>{ View::Reg64, View::Reg32 }
: std::vector<View>{ View::Reg32 };
break;
case View::Reg32_64:
return Is64BitWindows() ? std::vector<View>{ View::Reg32, View::Reg64 }
: std::vector<View>{ View::Reg32 };
break;
default:
return std::vector<View>{ view };
break;
}
}
#endif
cm::optional<std::string> cmWindowsRegistry::ReadValue(
cm::string_view key, cm::string_view name, View view,
cm::string_view separator)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
// compute list of registry views
auto views = this->ComputeViews(view);
if (cmsys::SystemTools::Strucmp(name.data(), "(default)") == 0) {
// handle magic name for default value
name = ""_s;
}
if (separator.empty()) {
separator = "\0"_s;
}
for (auto v : views) {
try {
this->LastError.clear();
auto handler = KeyHandler::OpenKey(key, v);
return handler.ReadValue(name, separator);
} catch (const registry_error& e) {
this->LastError = e.what();
continue;
}
}
#else
(void)key;
(void)name;
(void)view;
(void)separator;
#endif
return cm::nullopt;
}
cm::optional<std::vector<std::string>> cmWindowsRegistry::GetValueNames(
cm::string_view key, View view)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
this->LastError.clear();
// compute list of registry views
auto views = this->ComputeViews(view);
std::vector<std::string> valueNames;
bool querySuccessful = false;
for (auto v : views) {
try {
auto handler = KeyHandler::OpenKey(key, v);
auto list = handler.GetValueNames();
std::move(list.begin(), list.end(), std::back_inserter(valueNames));
querySuccessful = true;
} catch (const registry_error& e) {
this->LastError = e.what();
continue;
}
}
if (!valueNames.empty()) {
// value names must be unique and sorted
std::sort(valueNames.begin(), valueNames.end());
valueNames.erase(std::unique(valueNames.begin(), valueNames.end()),
valueNames.end());
}
if (querySuccessful) {
// At least one query was successful, so clean-up any error message
this->LastError.clear();
return valueNames;
}
#else
(void)key;
(void)view;
#endif
return cm::nullopt;
}
cm::optional<std::vector<std::string>> cmWindowsRegistry::GetSubKeys(
cm::string_view key, View view)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
this->LastError.clear();
// compute list of registry views
auto views = this->ComputeViews(view);
std::vector<std::string> subKeys;
bool querySuccessful = false;
for (auto v : views) {
try {
auto handler = KeyHandler::OpenKey(key, v);
auto list = handler.GetSubKeys();
std::move(list.begin(), list.end(), std::back_inserter(subKeys));
querySuccessful = true;
} catch (const registry_error& e) {
this->LastError = e.what();
continue;
}
}
if (!subKeys.empty()) {
// keys must be unique and sorted
std::sort(subKeys.begin(), subKeys.end());
subKeys.erase(std::unique(subKeys.begin(), subKeys.end()), subKeys.end());
}
if (querySuccessful) {
// At least one query was successful, so clean-up any error message
this->LastError.clear();
return subKeys;
}
#else
(void)key;
(void)view;
#endif
return cm::nullopt;
}

View File

@ -0,0 +1,55 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#pragma once
#include <string>
#include <vector>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/string_view>
class cmMakefile;
class cmWindowsRegistry
{
public:
cmWindowsRegistry(cmMakefile&);
enum class View
{
Both,
Target,
Host,
Reg64_32,
Reg32_64,
Reg32,
Reg64
};
cm::optional<std::string> ReadValue(cm::string_view key,
View view = View::Both,
cm::string_view separator = "\0"_s)
{
return this->ReadValue(key, ""_s, view, separator);
}
cm::optional<std::string> ReadValue(cm::string_view key,
cm::string_view name,
View view = View::Both,
cm::string_view separator = "\0"_s);
cm::optional<std::vector<std::string>> GetValueNames(cm::string_view key,
View view = View::Both);
cm::optional<std::vector<std::string>> GetSubKeys(cm::string_view key,
View view = View::Both);
cm::string_view GetLastError() const;
private:
#if defined(_WIN32) && !defined(__CYGWIN__)
std::vector<View> ComputeViews(View view);
int TargetSize = 0;
#endif
std::string LastError;
};

View File

@ -0,0 +1,4 @@
CMake Error at Registry_BadKey1.cmake:[0-9]+ \(message\):
WRONG_ROOT: invalid root key.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,4 @@
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY WRONG_ROOT/SUBKEY ERROR_VARIABLE error)
if (NOT error STREQUAL "")
message(FATAL_ERROR "${error}")
endif()

View File

@ -0,0 +1,4 @@
CMake Error at Registry_BadKey2.cmake:[0-9]+ \(message\):
HKLM-SUBKEY: invalid root key.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,4 @@
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY HKLM-SUBKEY ERROR_VARIABLE error)
if (NOT error STREQUAL "")
message(FATAL_ERROR "${error}")
endif()

View File

@ -0,0 +1,4 @@
CMake Error at Registry_BadQuery1.cmake:[0-9]+ \(cmake_host_system_information\):
cmake_host_system_information given invalid argument\(s\) "BAD_OPTION".
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY HKLM/SOFTWARE BAD_OPTION)

View File

@ -0,0 +1,5 @@
CMake Error at Registry_BadQuery2.cmake:[0-9]+ \(cmake_host_system_information\):
cmake_host_system_information missing expected value for argument\(s\)
"VALUE".
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY HKLM/SOFTWARE VALUE)

View File

@ -0,0 +1,5 @@
CMake Error at Registry_BadView1.cmake:[0-9]+ \(cmake_host_system_information\):
cmake_host_system_information missing expected value for argument\(s\)
"VIEW".
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY HKLM/SOFTWARE VIEW)

View File

@ -0,0 +1,4 @@
CMake Error at Registry_BadView2.cmake:[0-9]+ \(cmake_host_system_information\):
cmake_host_system_information given invalid value for "VIEW": BAD_VIEW.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY HKLM/SOFTWARE VIEW BAD_VIEW)

View File

@ -0,0 +1,4 @@
CMake Error at Registry_BadView3.cmake:[0-9]+ \(cmake_host_system_information\):
cmake_host_system_information given invalid argument\(s\) "64".
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY HKLM/SOFTWARE VIEW 32 64)

View File

@ -0,0 +1,4 @@
CMake Error at Registry_NoArgs.cmake:[0-9]+ \(cmake_host_system_information\):
cmake_host_system_information missing <key> specification.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY)

View File

@ -0,0 +1,232 @@
# check Windows architecture
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "HKCU" SUBKEYS VIEW 64 ERROR_VARIABLE status)
if (status STREQUAL "")
set(HOST_64BIT TRUE)
else()
set(HOST_64BIT FALSE)
endif()
# helper function for test validation
function(CHECK key result status expression)
if(status STREQUAL "")
cmake_language(EVAL CODE
"if (NOT (${expression}))
message(SEND_ERROR \"wrong value for key '${key}': '${result}'\")
endif()")
else()
message(SEND_ERROR "query failed for key '${key}': '${status}'")
endif()
endfunction()
# HKCU/Software/Classes/CLSID/CMake-Tests/chsi-registry: Query default value
set(KEY "HKCU/Software/Classes/CLSID/CMake-Tests/chsi-registry")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}"
"(HOST_64BIT AND result STREQUAL \"default 64bit\")
OR (NOT HOST_64BIT AND result STREQUAL \"default 32bit\")")
# query value using special name should be identical to default value
cmake_host_system_information(RESULT result2 QUERY WINDOWS_REGISTRY "${KEY}" VALUE "(default)" ERROR_VARIABLE status)
check("${KEY}{(default)}" "${result2}" "${status}" "result2 STREQUAL result")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VIEW HOST ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}"
"(HOST_64BIT AND result STREQUAL \"default 64bit\")
OR (NOT HOST_64BIT AND result STREQUAL \"default 32bit\")")
# VIEW TARGET should have same value as VIEW HOST
cmake_host_system_information(RESULT result2 QUERY WINDOWS_REGISTRY "${KEY}" VIEW TARGET ERROR_VARIABLE status)
check("${KEY}" "${result2}" "${status}" "result2 STREQUAL result")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VIEW 64 ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"default 64bit\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VIEW 32 ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"default 32bit\"")
# reg 64bit is read first
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VIEW 64_32 ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"default 64bit\"")
# reg 32bit is read first
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VIEW 32_64 ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"default 32bit\"")
# HKCU/Software/CMake-Tests/chsi-registry: Query named value
set(KEY "HKCU/Software/Classes/CLSID/CMake-Tests/chsi-registry")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE BYTE_SIZE
ERROR_VARIABLE status)
check("${KEY}{BYTE_SIZE}" "${result}" "${status}"
"(HOST_64BIT AND result STREQUAL \"64bit\")
OR (NOT HOST_64BIT AND result STREQUAL \"32bit\")")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE BYTE_SIZE
VIEW HOST ERROR_VARIABLE status)
check("${KEY}{BYTE_SIZE}" "${result}" "${status}"
"(HOST_64BIT AND result STREQUAL \"64bit\")
OR (NOT HOST_64BIT AND result STREQUAL \"32bit\")")
# VIEW TARGET should have same value as VIEW HOST
cmake_host_system_information(RESULT result2 QUERY WINDOWS_REGISTRY "${KEY}" VALUE BYTE_SIZE
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}{BYTE_SIZE}" "${result2}" "${status}" "result2 STREQUAL result")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE BYTE_SIZE
VIEW 64 ERROR_VARIABLE status)
check("${KEY}{BYTE_SIZE}" "${result}" "${status}" "result STREQUAL \"64bit\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE BYTE_SIZE
VIEW 32 ERROR_VARIABLE status)
check("${KEY}{BYTE_SIZE}" "${result}" "${status}" "result STREQUAL \"32bit\"")
# reg 64bit is read first
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE BYTE_SIZE
VIEW 64_32 ERROR_VARIABLE status)
check("${KEY}{BYTE_SIZE}" "${result}" "${status}" "result STREQUAL \"64bit\"")
# reg 32bit is read first
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE BYTE_SIZE
VIEW 32_64 ERROR_VARIABLE status)
check("${KEY}{BYTE_SIZE}" "${result}" "${status}" "result STREQUAL \"32bit\"")
# HKCU/Software/CMake-Tests/chsi-registry: check retrieval of various types
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE VALUE_SZ ERROR_VARIABLE status)
check("${KEY}{VALUE_SZ}" "${result}" "${status}" "result STREQUAL \"data with space\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE VALUE_EXPAND_SZ ERROR_VARIABLE status)
check("${KEY}{VALUE_EXPAND_SZ}" "${result}" "${status}"
"(NOT result STREQUAL \"PATH=%PATH%\") AND (result MATCHES \"^PATH=\")")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE VALUE_MULTI_SZ ERROR_VARIABLE status)
check("${KEY}{VALUE_MULTI_SZ}" "${result}" "${status}" "result STREQUAL \"data1;data2\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE VALUE2_MULTI_SZ
SEPARATOR "|" ERROR_VARIABLE status)
check("${KEY}{VALUE2_MULTI_SZ}" "${result}" "${status}" "result STREQUAL \"data1;data2\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE VALUE_DWORD ERROR_VARIABLE status)
check("${KEY}{VALUE_DWORD}" "${result}" "${status}" "result EQUAL \"129\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE VALUE_QWORD ERROR_VARIABLE status)
check("${KEY}{VALUE_QWORD}" "${result}" "${status}" "result EQUAL \"513\"")
# HKCU/Software/CMake-Tests/chsi-registry: check retrieval of value names
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
ERROR_VARIABLE status)
check("${KEY}[VALUE_NAMES]" "${result}" "${status}" "result STREQUAL \"(default);BYTE_SIZE;VALUE2_MULTI_SZ;VALUE2_SZ;VALUE_DWORD;VALUE_EXPAND_SZ;VALUE_MULTI_SZ;VALUE_QWORD;VALUE_SZ\"")
# VIEW BOTH should have same result as default view
cmake_host_system_information(RESULT result2 QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
VIEW BOTH ERROR_VARIABLE status)
check("${KEY}[VALUE_NAMES]" "${result2}" "${status}" "result STREQUAL \"(default);BYTE_SIZE;VALUE2_MULTI_SZ;VALUE2_SZ;VALUE_DWORD;VALUE_EXPAND_SZ;VALUE_MULTI_SZ;VALUE_QWORD;VALUE_SZ\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
VIEW HOST ERROR_VARIABLE status)
check("${KEY}[VALUE_NAMES]" "${result}" "${status}"
"(HOST_64BIT AND result STREQUAL \"(default);BYTE_SIZE;VALUE2_MULTI_SZ;VALUE_DWORD;VALUE_EXPAND_SZ;VALUE_MULTI_SZ;VALUE_QWORD;VALUE_SZ\")
OR (NOT HOST_64BIT AND result STREQUAL \"(default);BYTE_SIZE;VALUE2_SZ\")")
# VIEW TARGET should have same result as VIEW HOST
cmake_host_system_information(RESULT result2 QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}[VALUE_NAMES]" "${result2}" "${status}"
"(HOST_64BIT AND result STREQUAL \"(default);BYTE_SIZE;VALUE2_MULTI_SZ;VALUE_DWORD;VALUE_EXPAND_SZ;VALUE_MULTI_SZ;VALUE_QWORD;VALUE_SZ\")
OR (NOT HOST_64BIT AND result STREQUAL \"(default);BYTE_SIZE;VALUE2_SZ\")")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
VIEW 64 ERROR_VARIABLE status)
check("${KEY}[VALUE_NAMES]" "${result}" "${status}"
"result STREQUAL \"(default);BYTE_SIZE;VALUE2_MULTI_SZ;VALUE_DWORD;VALUE_EXPAND_SZ;VALUE_MULTI_SZ;VALUE_QWORD;VALUE_SZ\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
VIEW 32 ERROR_VARIABLE status)
check("${KEY}[VALUE_NAMES]" "${result}" "${status}" "result STREQUAL \"(default);BYTE_SIZE;VALUE2_SZ\"")
# reg 64bit is read first
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
VIEW 64_32 ERROR_VARIABLE status)
check("${KEY}[VALUE_NAMES]" "${result}" "${status}"
"result STREQUAL \"(default);BYTE_SIZE;VALUE2_MULTI_SZ;VALUE2_SZ;VALUE_DWORD;VALUE_EXPAND_SZ;VALUE_MULTI_SZ;VALUE_QWORD;VALUE_SZ\"")
# reg 32bit is read first. Result is the same as with view 64_32
cmake_host_system_information(RESULT result2 QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
VIEW 32_64 ERROR_VARIABLE status)
check("${KEY}[VALUE_NAMES]" "${result2}" "${status}" "result2 STREQUAL result")
# HKCU/Software/CMake-Tests/chsi-registry: check retrieval of sub keys
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" SUBKEYS
ERROR_VARIABLE status)
check("${KEY}[SUBKEYS]" "${result}" "${status}" "result STREQUAL \"subkey1;subkey2;subkey3\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" SUBKEYS
VIEW HOST ERROR_VARIABLE status)
check("${KEY}[SUBKEYS]" "${result}" "${status}"
"(HOST_64BIT AND result STREQUAL \"subkey1;subkey2\")
OR (NOT HOST_64BIT AND result STREQUAL \"subkey1;subkey3\")")
# VIEW TARGET should have same result as VIEW HOST
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" SUBKEYS
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}[SUBKEYS]" "${result}" "${status}"
"(HOST_64BIT AND result STREQUAL \"subkey1;subkey2\")
OR (NOT HOST_64BIT AND result STREQUAL \"subkey1;subkey3\")")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" SUBKEYS
VIEW 64 ERROR_VARIABLE status)
check("${KEY}[SUBKEYS]" "${result}" "${status}"
"result STREQUAL \"subkey1;subkey2\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" SUBKEYS
VIEW 32 ERROR_VARIABLE status)
check("${KEY}[SUBKEYS]" "${result}" "${status}"
"result STREQUAL \"subkey1;subkey3\"")
# reg 64bit is read first
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" SUBKEYS
VIEW 64_32 ERROR_VARIABLE status)
check("${KEY}[SUBLEYS]" "${result}" "${status}" "result STREQUAL \"subkey1;subkey2;subkey3\"")
# reg 32bit is read first. Result is the same as with view 64_32
cmake_host_system_information(RESULT result2 QUERY WINDOWS_REGISTRY "${KEY}" SUBKEYS
VIEW 32_64 ERROR_VARIABLE status)
check("${KEY}[SUBKEYS]" "${result2}" "${status}" "result2 STREQUAL result")
# Check influence of variable CMAKE_SIZEOF_VOID_P
set(CMAKE_SIZEOF_VOID_P 8)
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}"
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"default 64bit\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE BYTE_SIZE
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"64bit\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"(default);BYTE_SIZE;VALUE2_MULTI_SZ;VALUE_DWORD;VALUE_EXPAND_SZ;VALUE_MULTI_SZ;VALUE_QWORD;VALUE_SZ\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" SUBKEYS
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"subkey1;subkey2\"")
set(CMAKE_SIZEOF_VOID_P 4)
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}"
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"default 32bit\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE BYTE_SIZE
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"32bit\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" VALUE_NAMES
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"(default);BYTE_SIZE;VALUE2_SZ\"")
cmake_host_system_information(RESULT result QUERY WINDOWS_REGISTRY "${KEY}" SUBKEYS
VIEW TARGET ERROR_VARIABLE status)
check("${KEY}" "${result}" "${status}" "result STREQUAL \"subkey1;subkey3\"")

View File

@ -21,3 +21,27 @@ if(RunCMake_GENERATOR MATCHES "^Visual Studio " AND NOT RunCMake_GENERATOR STREQ
else() else()
run_cmake(VsMSBuildMissing) run_cmake(VsMSBuildMissing)
endif() endif()
# WINDOWS_REGISTRY tests
run_cmake(Registry_NoArgs)
run_cmake(Registry_BadQuery1)
run_cmake(Registry_BadQuery2)
run_cmake(Registry_BadView1)
run_cmake(Registry_BadView2)
run_cmake(Registry_BadView3)
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
run_cmake(Registry_BadKey1)
run_cmake(Registry_BadKey2)
# Tests using the Windows registry
find_program(REG NAMES "reg.exe" NO_CACHE)
if (REG)
# crete some entries in the registry
cmake_path(CONVERT "${RunCMake_SOURCE_DIR}/registry_data.reg" TO_NATIVE_PATH_LIST registry_data)
execute_process(COMMAND "${REG}" import "${registry_data}" OUTPUT_QUIET ERROR_QUIET)
run_cmake(Registry_Query)
# clean-up registry
execute_process(COMMAND "${REG}" delete "HKCU\\SOFTWARE\\Classes\\CLSID\\CMake-Tests" /f OUTPUT_QUIET ERROR_QUIET)
execute_process(COMMAND "${REG}" delete "HKCU\\SOFTWARE\\Classes\\WOW6432Node\\CLSID\\CMake-Tests" /f OUTPUT_QUIET ERROR_QUIET)
endif()
endif()