diff options
14 files changed, 336 insertions, 7 deletions
diff --git a/Help/command/cmake_host_system_information.rst b/Help/command/cmake_host_system_information.rst index ac6a008..ae24b03 100644 --- a/Help/command/cmake_host_system_information.rst +++ b/Help/command/cmake_host_system_information.rst @@ -132,7 +132,52 @@ queried. The list of queried values is stored in ``<variable>``. See :variable:`CMAKE_HOST_SYSTEM_PROCESSOR` +For Linux distributions additional ``<key>`` values are available to get operating +system identification as described in the `man 5 os-release`_. + +``DISTRIB_INFO`` + .. versionadded:: 3.22 + + Read :file:`/etc/os-release` file and define the given ``<variable>`` + into a list of read variables + +``DISTRIB_<name>`` + .. versionadded:: 3.22 + + Get the ``<name>`` variable if it exists in the :file:`/etc/os-release` file + + Example: + + .. code-block:: cmake + + cmake_host_system_information(RESULT PRETTY_NAME QUERY DISTRIB_PRETTY_NAME) + message(STATUS "${PRETTY_NAME}") + + cmake_host_system_information(RESULT DISTRO QUERY DISTRIB_INFO) + + foreach(VAR IN LISTS DISTRO) + message(STATUS "${VAR}=`${${VAR}}`") + endforeach() + + + Output:: + + -- Ubuntu 20.04.2 LTS + -- DISTRO_BUG_REPORT_URL=`https://bugs.launchpad.net/ubuntu/` + -- DISTRO_HOME_URL=`https://www.ubuntu.com/` + -- DISTRO_ID=`ubuntu` + -- DISTRO_ID_LIKE=`debian` + -- DISTRO_NAME=`Ubuntu` + -- DISTRO_PRETTY_NAME=`Ubuntu 20.04.2 LTS` + -- DISTRO_PRIVACY_POLICY_URL=`https://www.ubuntu.com/legal/terms-and-policies/privacy-policy` + -- DISTRO_SUPPORT_URL=`https://help.ubuntu.com/` + -- DISTRO_UBUNTU_CODENAME=`focal` + -- DISTRO_VERSION=`20.04.2 LTS (Focal Fossa)` + -- DISTRO_VERSION_CODENAME=`focal` + -- DISTRO_VERSION_ID=`20.04` .. rubric:: Footnotes .. [#mebibytes] One MiB (mebibyte) is equal to 1024x1024 bytes. + +.. _man 5 os-release: https://www.freedesktop.org/software/systemd/man/os-release.html diff --git a/Help/release/dev/os-release.rst b/Help/release/dev/os-release.rst new file mode 100644 index 0000000..cce6aee --- /dev/null +++ b/Help/release/dev/os-release.rst @@ -0,0 +1,6 @@ +os-release +---------- + +* The :command:`cmake_host_system_information` command query operating system + identification `variables <https://www.freedesktop.org/software/systemd/man/os-release.html>`_ + from the :file:`/etc/os-release` file. diff --git a/Source/cmCMakeHostSystemInformationCommand.cxx b/Source/cmCMakeHostSystemInformationCommand.cxx index 9e21208..cdd6b4c 100644 --- a/Source/cmCMakeHostSystemInformationCommand.cxx +++ b/Source/cmCMakeHostSystemInformationCommand.cxx @@ -2,28 +2,37 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmCMakeHostSystemInformationCommand.h" -#include <cstddef> +#include <cassert> +#include <cctype> +#include <initializer_list> +#include <map> +#include <string> +#include <type_traits> +#include <utility> #include <cm/optional> #include <cm/string_view> #include <cmext/string_view> +#include "cmsys/FStream.hxx" #include "cmsys/SystemInformation.hxx" #include "cmExecutionStatus.h" #include "cmMakefile.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" #ifdef _WIN32 # include "cmAlgorithms.h" # include "cmGlobalGenerator.h" # include "cmGlobalVisualStudioVersionedGenerator.h" -# include "cmStringAlgorithms.h" -# include "cmSystemTools.h" # include "cmVSSetupHelper.h" # define HAVE_VS_SETUP_HELPER #endif namespace { +std::string const DELIM[2] = { {}, ";" }; + // BEGIN Private functions std::string ValueToString(std::size_t const value) { @@ -138,6 +147,177 @@ cm::optional<std::string> GetValue(cmsys::SystemInformation& info, return {}; } +#ifdef __linux__ +cm::optional<std::pair<std::string, std::string>> ParseOSReleaseLine( + std::string const& line) +{ + std::string key; + std::string value; + + char prev = 0; + enum ParserState + { + PARSE_KEY_1ST, + PARSE_KEY, + FOUND_EQ, + PARSE_SINGLE_QUOTE_VALUE, + PARSE_DBL_QUOTE_VALUE, + PARSE_VALUE, + IGNORE_REST + } state = PARSE_KEY_1ST; + + for (auto ch : line) { + switch (state) { + case PARSE_KEY_1ST: + if (std::isalpha(ch) || ch == '_') { + key += ch; + state = PARSE_KEY; + } else if (!std::isspace(ch)) { + state = IGNORE_REST; + } + break; + + case PARSE_KEY: + if (ch == '=') { + state = FOUND_EQ; + } else if (std::isalnum(ch) || ch == '_') { + key += ch; + } else { + state = IGNORE_REST; + } + break; + + case FOUND_EQ: + switch (ch) { + case '\'': + state = PARSE_SINGLE_QUOTE_VALUE; + break; + case '"': + state = PARSE_DBL_QUOTE_VALUE; + break; + case '#': + case '\\': + state = IGNORE_REST; + break; + default: + value += ch; + state = PARSE_VALUE; + } + break; + + case PARSE_SINGLE_QUOTE_VALUE: + if (ch == '\'') { + if (prev != '\\') { + state = IGNORE_REST; + } else { + assert(!value.empty()); + value[value.size() - 1] = ch; + } + } else { + value += ch; + } + break; + + case PARSE_DBL_QUOTE_VALUE: + if (ch == '"') { + if (prev != '\\') { + state = IGNORE_REST; + } else { + assert(!value.empty()); + value[value.size() - 1] = ch; + } + } else { + value += ch; + } + break; + + case PARSE_VALUE: + if (ch == '#' || std::isspace(ch)) { + state = IGNORE_REST; + } else { + value += ch; + } + break; + + default: + // Unexpected os-release parser state! + state = IGNORE_REST; + break; + } + + if (state == IGNORE_REST) { + break; + } + prev = ch; + } + if (!(key.empty() || value.empty())) { + return std::make_pair(key, value); + } + return {}; +} + +std::map<std::string, std::string> GetOSReleaseVariables( + cmExecutionStatus& status) +{ + const auto& sysroot = + status.GetMakefile().GetSafeDefinition("CMAKE_SYSROOT"); + + std::map<std::string, std::string> data; + // Based on + // https://www.freedesktop.org/software/systemd/man/os-release.html + for (auto name : { "/etc/os-release"_s, "/usr/lib/os-release"_s }) { + const auto& filename = cmStrCat(sysroot, name); + if (cmSystemTools::FileExists(filename)) { + cmsys::ifstream fin(filename.c_str()); + for (std::string line; !std::getline(fin, line).fail();) { + auto kv = ParseOSReleaseLine(line); + if (kv.has_value()) { + data.emplace(kv.value()); + } + } + break; + } + } + return data; +} + +cm::optional<std::string> GetValue(cmExecutionStatus& status, + std::string const& key, + std::string const& variable) +{ + const auto prefix = "DISTRIB_"_s; + if (!cmHasPrefix(key, prefix)) { + return {}; + } + + static const std::map<std::string, std::string> s_os_release = + GetOSReleaseVariables(status); + + auto& makefile = status.GetMakefile(); + + const std::string subkey = + key.substr(prefix.size(), key.size() - prefix.size()); + if (subkey == "INFO"_s) { + std::string vars; + for (const auto& kv : s_os_release) { + auto cmake_var_name = cmStrCat(variable, '_', kv.first); + vars += DELIM[!vars.empty()] + cmake_var_name; + makefile.AddDefinition(cmake_var_name, kv.second); + } + return cm::optional<std::string>(std::move(vars)); + } + + // Query individual variable + const auto it = s_os_release.find(subkey); + if (it != s_os_release.cend()) { + return it->second; + } + + // NOTE Empty string means requested variable not set + return std::string{}; +} +#endif + #ifdef HAVE_VS_SETUP_HELPER cm::optional<std::string> GetValue(cmExecutionStatus& status, std::string const& key) @@ -201,10 +381,9 @@ bool cmCMakeHostSystemInformationCommand(std::vector<std::string> const& args, std::string result_list; for (auto i = current_index + 1; i < args.size(); ++i) { + result_list += DELIM[!result_list.empty()]; + auto const& key = args[i]; - if (i != current_index + 1) { - result_list += ";"; - } auto value = GetValue(info, key); if (!value) { #ifdef HAVE_VS_SETUP_HELPER @@ -213,6 +392,12 @@ bool cmCMakeHostSystemInformationCommand(std::vector<std::string> const& args, status.SetError("does not recognize <key> " + key); return false; } +#elif defined(__linux__) + value = GetValue(status, key, variable); + if (!value) { + status.SetError("does not recognize <key> " + key); + return false; + } #else status.SetError("does not recognize <key> " + key); return false; diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index eb2c2d9..543ad1d 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -349,7 +349,7 @@ if(NOT CMake_TEST_EXTERNAL_CMAKE) endif() add_RunCMake_test(execute_process) add_RunCMake_test(export) -add_RunCMake_test(cmake_host_system_information) +add_RunCMake_test(cmake_host_system_information -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}) add_RunCMake_test(cmake_language) add_RunCMake_test(cmake_minimum_required) add_RunCMake_test(cmake_parse_arguments) diff --git a/Tests/RunCMake/cmake_host_system_information/Exherbo-stdout.txt b/Tests/RunCMake/cmake_host_system_information/Exherbo-stdout.txt new file mode 100644 index 0000000..11ae71f --- /dev/null +++ b/Tests/RunCMake/cmake_host_system_information/Exherbo-stdout.txt @@ -0,0 +1,9 @@ +-- TEST1_ANSI_COLOR=`0;32` +-- TEST1_BUG_REPORT_URL=`https://bugs.exherbo.org/` +-- TEST1_HOME_URL=`https://www.exherbo.org/` +-- TEST1_ID=`exherbo` +-- TEST1_NAME=`Exherbo` +-- TEST1_PRETTY_NAME=`Exherbo Linux` +-- TEST1_SUPPORT_URL=`irc://irc.freenode.net/#exherbo` +-- TEST2_ID=`exherbo` +-- TEST2_VERSION=`` diff --git a/Tests/RunCMake/cmake_host_system_information/Exherbo.cmake b/Tests/RunCMake/cmake_host_system_information/Exherbo.cmake new file mode 100644 index 0000000..7fc26d8 --- /dev/null +++ b/Tests/RunCMake/cmake_host_system_information/Exherbo.cmake @@ -0,0 +1,11 @@ +cmake_host_system_information(RESULT TEST1 QUERY DISTRIB_INFO) + +foreach(VAR IN LISTS TEST1) + message(STATUS "${VAR}=`${${VAR}}`") +endforeach() + +# Query individual variables +cmake_host_system_information(RESULT TEST2 QUERY DISTRIB_ID DISTRIB_VERSION) +list(POP_FRONT TEST2 TEST2_ID TEST2_VERSION) +message(STATUS "TEST2_ID=`${TEST2_ID}`") +message(STATUS "TEST2_VERSION=`${TEST2_VERSION}`") diff --git a/Tests/RunCMake/cmake_host_system_information/Exherbo/etc/os-release b/Tests/RunCMake/cmake_host_system_information/Exherbo/etc/os-release new file mode 100644 index 0000000..944c9b4 --- /dev/null +++ b/Tests/RunCMake/cmake_host_system_information/Exherbo/etc/os-release @@ -0,0 +1,7 @@ +NAME="Exherbo" +PRETTY_NAME="Exherbo Linux" +ID="exherbo" +ANSI_COLOR="0;32" +HOME_URL="https://www.exherbo.org/" +SUPPORT_URL="irc://irc.freenode.net/#exherbo" +BUG_REPORT_URL="https://bugs.exherbo.org/" diff --git a/Tests/RunCMake/cmake_host_system_information/RunCMakeTest.cmake b/Tests/RunCMake/cmake_host_system_information/RunCMakeTest.cmake index be74ade..6cbb7fd 100644 --- a/Tests/RunCMake/cmake_host_system_information/RunCMakeTest.cmake +++ b/Tests/RunCMake/cmake_host_system_information/RunCMakeTest.cmake @@ -6,3 +6,11 @@ run_cmake(BadArg3) run_cmake(QueryList) run_cmake(QueryKeys) + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + + run_cmake_with_options(UnitTest) + run_cmake_with_options(Exherbo) + run_cmake_with_options(Ubuntu) + +endif() diff --git a/Tests/RunCMake/cmake_host_system_information/Ubuntu-stdout.txt b/Tests/RunCMake/cmake_host_system_information/Ubuntu-stdout.txt new file mode 100644 index 0000000..d1a18da --- /dev/null +++ b/Tests/RunCMake/cmake_host_system_information/Ubuntu-stdout.txt @@ -0,0 +1,14 @@ +-- TEST1_BUG_REPORT_URL=`https://bugs\.launchpad\.net/ubuntu/` +-- TEST1_HOME_URL=`https://www\.ubuntu\.com/` +-- TEST1_ID=`ubuntu` +-- TEST1_ID_LIKE=`debian` +-- TEST1_NAME=`Ubuntu` +-- TEST1_PRETTY_NAME=`Ubuntu 20\.04\.2 LTS` +-- TEST1_PRIVACY_POLICY_URL=`https://www\.ubuntu\.com/legal/terms-and-policies/privacy-policy` +-- TEST1_SUPPORT_URL=`https://help\.ubuntu\.com/` +-- TEST1_UBUNTU_CODENAME=`focal` +-- TEST1_VERSION=`20\.04\.2 LTS \(Focal Fossa\)` +-- TEST1_VERSION_CODENAME=`focal` +-- TEST1_VERSION_ID=`20\.04` +-- TEST2_ID=`ubuntu` +-- TEST2_VERSION=`20\.04\.2 LTS \(Focal Fossa\)` diff --git a/Tests/RunCMake/cmake_host_system_information/Ubuntu.cmake b/Tests/RunCMake/cmake_host_system_information/Ubuntu.cmake new file mode 100644 index 0000000..7fc26d8 --- /dev/null +++ b/Tests/RunCMake/cmake_host_system_information/Ubuntu.cmake @@ -0,0 +1,11 @@ +cmake_host_system_information(RESULT TEST1 QUERY DISTRIB_INFO) + +foreach(VAR IN LISTS TEST1) + message(STATUS "${VAR}=`${${VAR}}`") +endforeach() + +# Query individual variables +cmake_host_system_information(RESULT TEST2 QUERY DISTRIB_ID DISTRIB_VERSION) +list(POP_FRONT TEST2 TEST2_ID TEST2_VERSION) +message(STATUS "TEST2_ID=`${TEST2_ID}`") +message(STATUS "TEST2_VERSION=`${TEST2_VERSION}`") diff --git a/Tests/RunCMake/cmake_host_system_information/Ubuntu/etc/os-release b/Tests/RunCMake/cmake_host_system_information/Ubuntu/etc/os-release new file mode 100644 index 0000000..f228f22 --- /dev/null +++ b/Tests/RunCMake/cmake_host_system_information/Ubuntu/etc/os-release @@ -0,0 +1,12 @@ +NAME="Ubuntu" +VERSION="20.04.2 LTS (Focal Fossa)" +ID=ubuntu +ID_LIKE=debian +PRETTY_NAME="Ubuntu 20.04.2 LTS" +VERSION_ID="20.04" +HOME_URL="https://www.ubuntu.com/" +SUPPORT_URL="https://help.ubuntu.com/" +BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" +PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" +VERSION_CODENAME=focal +UBUNTU_CODENAME=focal diff --git a/Tests/RunCMake/cmake_host_system_information/UnitTest-stdout.txt b/Tests/RunCMake/cmake_host_system_information/UnitTest-stdout.txt new file mode 100644 index 0000000..db6f487 --- /dev/null +++ b/Tests/RunCMake/cmake_host_system_information/UnitTest-stdout.txt @@ -0,0 +1,7 @@ +-- UNIT_TEST_A_LIST_LIKE_VARIABLE=`satu;dua;tiga` +-- UNIT_TEST_DBL_QUOTED_VALUE=`"The" value in double "quotes"` +-- UNIT_TEST_DBL_QUOTED_VALUE_STIPPED_COMMENT=`Blah blah blah` +-- UNIT_TEST_NON_SPACE_VALUE=`Blah-blah-blah` +-- UNIT_TEST_QUOTED_VALUE=`'The' value in single 'quotes'` +-- UNIT_TEST_QUOTED_VALUE_STIPPED_COMMENT=`The value in single quotes` +-- UNIT_TEST_THE_URL_WITH_ANCHOR_TEST=`https://blah.blah/resource#anchor` diff --git a/Tests/RunCMake/cmake_host_system_information/UnitTest.cmake b/Tests/RunCMake/cmake_host_system_information/UnitTest.cmake new file mode 100644 index 0000000..d9a0aca --- /dev/null +++ b/Tests/RunCMake/cmake_host_system_information/UnitTest.cmake @@ -0,0 +1,5 @@ +cmake_host_system_information(RESULT UNIT_TEST QUERY DISTRIB_INFO) + +foreach(VAR IN LISTS UNIT_TEST) + message(STATUS "${VAR}=`${${VAR}}`") +endforeach() diff --git a/Tests/RunCMake/cmake_host_system_information/UnitTest/etc/os-release b/Tests/RunCMake/cmake_host_system_information/UnitTest/etc/os-release new file mode 100644 index 0000000..66c33b5 --- /dev/null +++ b/Tests/RunCMake/cmake_host_system_information/UnitTest/etc/os-release @@ -0,0 +1,9 @@ +# Comment string gonna be ignored +NON_SPACE_VALUE=Blah-blah-blah +QUOTED_VALUE='\'The\' value in single \'quotes\'' +QUOTED_VALUE_STIPPED_COMMENT='The value in single quotes'# The comment right after `'` +DBL_QUOTED_VALUE="\"The\" value in double \"quotes\"" +DBL_QUOTED_VALUE_STIPPED_COMMENT="Blah blah blah"# The comment right after `'` +THE_URL_WITH_ANCHOR_TEST="https://blah.blah/resource#anchor" # And a comment after +A_LIST_LIKE_VARIABLE='satu;dua;tiga' +INCORRECT_ESCAPE_IGNORED=\'This line gonna be ignored' |