diff --git a/Help/command/string.rst b/Help/command/string.rst index abde6eeab..8ed0e86d9 100644 --- a/Help/command/string.rst +++ b/Help/command/string.rst @@ -36,6 +36,8 @@ String operations. string(TIMESTAMP [] [UTC]) string(MAKE_C_IDENTIFIER ) string(GENEX_STRIP ) + string(UUID NAMESPACE NAME + TYPE ) REGEX MATCH will match the regular expression once and store the match in the output variable. @@ -159,3 +161,13 @@ identifier in C. ``GENEX_STRIP`` will strip any :manual:`generator expressions ` from the ``input string`` and store the result in the ``output variable``. + +UUID creates a univerally unique identifier (aka GUID) as per RFC4122 +based on the hash of the combined values of +(which itself has to be a valid UUID) and . +The hash algorithm can be either ``MD5`` (Version 3 UUID) or +``SHA1`` (Version 5 UUID). +A UUID has the format ``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`` +where each `x` represents a lower case hexadecimal character. +Where required an uppercase representation can be requested +with the optional ``UPPER`` flag. diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index ff7bc8da9..c3f77f472 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -308,6 +308,7 @@ set(SRCS cmTest.h cmTestGenerator.cxx cmTestGenerator.h + cmUuid.cxx cmVariableWatch.cxx cmVariableWatch.h cmVersion.cxx diff --git a/Source/cmStringCommand.cxx b/Source/cmStringCommand.cxx index 65912da94..90a8f8546 100644 --- a/Source/cmStringCommand.cxx +++ b/Source/cmStringCommand.cxx @@ -20,6 +20,7 @@ #include #include +#include //---------------------------------------------------------------------------- bool cmStringCommand @@ -105,6 +106,10 @@ bool cmStringCommand { return this->HandleGenexStripCommand(args); } + else if(subCommand == "UUID") + { + return this->HandleUuidCommand(args); + } std::string e = "does not recognize sub-command "+subCommand; this->SetError(e); @@ -981,3 +986,114 @@ bool cmStringCommand return true; } + +bool cmStringCommand +::HandleUuidCommand(std::vector const& args) +{ +#if defined(CMAKE_BUILD_WITH_CMAKE) + unsigned int argsIndex = 1; + + if(args.size() < 2) + { + this->SetError("UUID sub-command requires an output variable."); + return false; + } + + const std::string &outputVariable = args[argsIndex++]; + + std::string uuidNamespaceString; + std::string uuidName; + std::string uuidType; + bool uuidUpperCase = false; + + while(args.size() > argsIndex) + { + if(args[argsIndex] == "NAMESPACE") + { + ++argsIndex; + if(argsIndex >= args.size()) + { + this->SetError("UUID sub-command, NAMESPACE requires a value."); + return false; + } + uuidNamespaceString = args[argsIndex++]; + } + else if(args[argsIndex] == "NAME") + { + ++argsIndex; + if(argsIndex >= args.size()) + { + this->SetError("UUID sub-command, NAME requires a value."); + return false; + } + uuidName = args[argsIndex++]; + } + else if(args[argsIndex] == "TYPE") + { + ++argsIndex; + if(argsIndex >= args.size()) + { + this->SetError("UUID sub-command, TYPE requires a value."); + return false; + } + uuidType = args[argsIndex++]; + } + else if(args[argsIndex] == "UPPER") + { + ++argsIndex; + uuidUpperCase = true; + } + else + { + std::string e = "UUID sub-command does not recognize option " + + args[argsIndex] + "."; + this->SetError(e); + return false; + } + } + + std::string uuid; + cmUuid uuidGenerator; + + std::vector uuidNamespace; + if(!uuidGenerator.StringToBinary(uuidNamespaceString, uuidNamespace)) + { + this->SetError("UUID sub-command, malformed NAMESPACE UUID."); + return false; + } + + if(uuidType == "MD5") + { + uuid = uuidGenerator.FromMd5(uuidNamespace, uuidName); + } + else if(uuidType == "SHA1") + { + uuid = uuidGenerator.FromSha1(uuidNamespace, uuidName); + } + else + { + std::string e = "UUID sub-command, unknown TYPE '" + uuidType + "'."; + this->SetError(e); + return false; + } + + if(uuid.empty()) + { + this->SetError("UUID sub-command, generation failed."); + return false; + } + + if(uuidUpperCase) + { + uuid = cmSystemTools::UpperCase(uuid); + } + + this->Makefile->AddDefinition(outputVariable, uuid.c_str()); + return true; +#else + cmOStringStream e; + e << args[0] << " not available during bootstrap"; + this->SetError(e.str().c_str()); + return false; +#endif +} diff --git a/Source/cmStringCommand.h b/Source/cmStringCommand.h index 8292e643d..9c75095c1 100644 --- a/Source/cmStringCommand.h +++ b/Source/cmStringCommand.h @@ -74,6 +74,7 @@ protected: bool HandleTimestampCommand(std::vector const& args); bool HandleMakeCIdentifierCommand(std::vector const& args); bool HandleGenexStripCommand(std::vector const& args); + bool HandleUuidCommand(std::vector const& args); class RegexReplacement { diff --git a/Source/cmUuid.cxx b/Source/cmUuid.cxx new file mode 100644 index 000000000..8b5b7aef0 --- /dev/null +++ b/Source/cmUuid.cxx @@ -0,0 +1,214 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2014 Kitware, Inc., Insight Software Consortium + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ +#include "cmUuid.h" + +#include + +#include +#include "cm_sha2.h" + +cmUuid::cmUuid() +{ + Groups.push_back(4); + Groups.push_back(2); + Groups.push_back(2); + Groups.push_back(2); + Groups.push_back(6); +} + +std::string cmUuid::FromMd5(std::vector const& uuidNamespace, + std::string const& name) const +{ + std::vector hashInput; + this->CreateHashInput(uuidNamespace, name, hashInput); + + cmsysMD5_s *md5 = cmsysMD5_New(); + cmsysMD5_Initialize(md5); + cmsysMD5_Append(md5, &hashInput[0], int(hashInput.size())); + + unsigned char digest[16] = {0}; + cmsysMD5_Finalize(md5, digest); + + cmsysMD5_Delete(md5); + + return this->FromDigest(digest, 3); +} + +std::string cmUuid::FromSha1(std::vector const& uuidNamespace, + std::string const& name) const +{ + std::vector hashInput; + this->CreateHashInput(uuidNamespace, name, hashInput); + + SHA_CTX *sha = new SHA_CTX; + SHA1_Init(sha); + SHA1_Update(sha, &hashInput[0], hashInput.size()); + + unsigned char digest[SHA1_DIGEST_LENGTH] = {0}; + SHA1_Final(digest, sha); + + delete sha; + + return this->FromDigest(digest, 5); +} + +void cmUuid::CreateHashInput(std::vector const& uuidNamespace, + std::string const& name, std::vector &output) const +{ + output = uuidNamespace; + + if(name.size()) + { + output.resize(output.size() + name.size()); + + memcpy(&output[0] + uuidNamespace.size(), + name.c_str(), name.size()); + } +} + +std::string cmUuid::FromDigest( + const unsigned char* digest, unsigned char version) const +{ + typedef unsigned char byte_t; + + byte_t uuid[16] = {0}; + memcpy(uuid, digest, 16); + + uuid[6] &= 0xF; + uuid[6] |= byte_t(version << 4); + + uuid[8] &= 0x3F; + uuid[8] |= 0x80; + + return this->BinaryToString(uuid); +} + +bool cmUuid::StringToBinary(std::string const& input, + std::vector &output) const +{ + output.clear(); + output.reserve(16); + + if(input.length() != 36) + { + return false; + } + size_t index = 0; + for(size_t i = 0; i < this->Groups.size(); ++i) + { + if(i != 0 && input[index++] != '-') + { + return false; + } + size_t digits = this->Groups[i] * 2; + if(!StringToBinaryImpl(input.substr(index, digits), output)) + { + return false; + } + + index += digits; + } + + return true; +} + +std::string cmUuid::BinaryToString(const unsigned char* input) const +{ + std::string output; + + size_t inputIndex = 0; + for(size_t i = 0; i < this->Groups.size(); ++i) + { + if(i != 0) + { + output += '-'; + } + + size_t bytes = this->Groups[i]; + for(size_t j = 0; j < bytes; ++j) + { + unsigned char byte = input[inputIndex++]; + output += this->ByteToHex(byte); + } + } + + return output; +} + +std::string cmUuid::ByteToHex(unsigned char byte) const +{ + std::string result; + for(int i = 0; i < 2; ++i) + { + unsigned char rest = byte % 16; + byte /= 16; + + char c = (rest < 0xA) ? + char('0' + rest) : + char('a' + (rest - 0xA)); + + result = c + result; + } + + return result; +} + +bool cmUuid::StringToBinaryImpl(std::string const& input, + std::vector &output) const +{ + if(input.size()%2) + { + return false; + } + + for(size_t i = 0; i < input.size(); i +=2) + { + char c1 = 0; + if(!IntFromHexDigit(input[i], c1)) + { + return false; + } + + char c2 = 0; + if(!IntFromHexDigit(input[i + 1], c2)) + { + return false; + } + + output.push_back(char(c1 << 4 | c2)); + } + + return true; +} + +bool cmUuid::IntFromHexDigit(char input, char& output) const +{ + if(input >= '0' && input <= '9') + { + output = char(input - '0'); + return true; + } + else if(input >= 'a' && input <= 'f') + { + output = char(input - 'a' + 0xA); + return true; + } + else if(input >= 'A' && input <= 'F') + { + output = char(input - 'A' + 0xA); + return true; + } + else + { + return false; + } +} diff --git a/Source/cmUuid.h b/Source/cmUuid.h new file mode 100644 index 000000000..0dda35758 --- /dev/null +++ b/Source/cmUuid.h @@ -0,0 +1,55 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2014 Kitware, Inc., Insight Software Consortium + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ +#ifndef cmUuid_h +#define cmUuid_h + +#include "cmStandardIncludes.h" + +/** \class cmUuid + * \brief Utility class to generate UUIDs as defined by RFC4122 + * + */ +class cmUuid +{ +public: + cmUuid(); + + std::string FromMd5(std::vector const& uuidNamespace, + std::string const& name) const; + + std::string FromSha1(std::vector const& uuidNamespace, + std::string const& name) const; + + bool StringToBinary(std::string const& input, + std::vector &output) const; + +private: + std::string ByteToHex(unsigned char byte) const; + + void CreateHashInput(std::vector const& uuidNamespace, + std::string const& name, std::vector &output) const; + + std::string FromDigest(const unsigned char* digest, + unsigned char version) const; + + bool StringToBinaryImpl(std::string const& input, + std::vector &output) const; + + std::string BinaryToString(const unsigned char* input) const; + + bool IntFromHexDigit(char input, char& output) const; + + std::vector Groups; +}; + + +#endif diff --git a/Tests/RunCMake/string/RunCMakeTest.cmake b/Tests/RunCMake/string/RunCMakeTest.cmake index 501acd2bd..e83db274d 100644 --- a/Tests/RunCMake/string/RunCMakeTest.cmake +++ b/Tests/RunCMake/string/RunCMakeTest.cmake @@ -2,3 +2,11 @@ include(RunCMake) run_cmake(Concat) run_cmake(ConcatNoArgs) + +run_cmake(Uuid) +run_cmake(UuidMissingNamespace) +run_cmake(UuidMissingNamespaceValue) +run_cmake(UuidBadNamespace) +run_cmake(UuidMissingNameValue) +run_cmake(UuidMissingTypeValue) +run_cmake(UuidBadType) diff --git a/Tests/RunCMake/string/Uuid.cmake b/Tests/RunCMake/string/Uuid.cmake new file mode 100644 index 000000000..2613d2682 --- /dev/null +++ b/Tests/RunCMake/string/Uuid.cmake @@ -0,0 +1,17 @@ +set(UUID_DNS_NAMESPACE 6ba7b810-9dad-11d1-80b4-00c04fd430c8) + +string(UUID WWW_EXAMPLE_COM_MD5_UUID + NAMESPACE ${UUID_DNS_NAMESPACE} NAME www.example.com TYPE MD5) + +if(NOT WWW_EXAMPLE_COM_MD5_UUID STREQUAL "5df41881-3aed-3515-88a7-2f4a814cf09e") + message(SEND_ERROR + "UUID did not create the expected MD5 result: ${WWW_EXAMPLE_COM_MD5_UUID}") +endif() + +string(UUID WWW_EXAMPLE_COM_SHA1_UUID + NAMESPACE ${UUID_DNS_NAMESPACE} NAME www.example.com TYPE SHA1 UPPER) + +if(NOT WWW_EXAMPLE_COM_SHA1_UUID STREQUAL "2ED6657D-E927-568B-95E1-2665A8AEA6A2") + message(SEND_ERROR + "UUID did not create the expected SHA1 result: ${WWW_EXAMPLE_COM_SHA1_UUID}") +endif() diff --git a/Tests/RunCMake/string/UuidBadNamespace-result.txt b/Tests/RunCMake/string/UuidBadNamespace-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/string/UuidBadNamespace-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/UuidBadNamespace-stderr.txt b/Tests/RunCMake/string/UuidBadNamespace-stderr.txt new file mode 100644 index 000000000..cb12903e1 --- /dev/null +++ b/Tests/RunCMake/string/UuidBadNamespace-stderr.txt @@ -0,0 +1,4 @@ +CMake Error at UuidBadNamespace.cmake:3 \(string\): + string UUID sub-command, malformed NAMESPACE UUID. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/string/UuidBadNamespace.cmake b/Tests/RunCMake/string/UuidBadNamespace.cmake new file mode 100644 index 000000000..f6079252b --- /dev/null +++ b/Tests/RunCMake/string/UuidBadNamespace.cmake @@ -0,0 +1,4 @@ +set(UUID_DNS_NAMESPACE 6ba7b810-9dad-fooo-80b4-00c04fd430c8) + +string(UUID WWW_EXAMPLE_COM_MD5_UUID + NAMESPACE ${UUID_DNS_NAMESPACE} NAME www.example.com TYPE MD5) diff --git a/Tests/RunCMake/string/UuidBadType-result.txt b/Tests/RunCMake/string/UuidBadType-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/string/UuidBadType-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/UuidBadType-stderr.txt b/Tests/RunCMake/string/UuidBadType-stderr.txt new file mode 100644 index 000000000..1993c044a --- /dev/null +++ b/Tests/RunCMake/string/UuidBadType-stderr.txt @@ -0,0 +1,4 @@ +CMake Error at UuidBadType.cmake:3 \(string\): + string UUID sub-command, unknown TYPE 'FOO'. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/string/UuidBadType.cmake b/Tests/RunCMake/string/UuidBadType.cmake new file mode 100644 index 000000000..bf4909e9c --- /dev/null +++ b/Tests/RunCMake/string/UuidBadType.cmake @@ -0,0 +1,4 @@ +set(UUID_DNS_NAMESPACE 6ba7b810-9dad-11d1-80b4-00c04fd430c8) + +string(UUID WWW_EXAMPLE_COM_MD5_UUID + NAMESPACE ${UUID_DNS_NAMESPACE} NAME www.example.com TYPE FOO) diff --git a/Tests/RunCMake/string/UuidMissingNameValue-result.txt b/Tests/RunCMake/string/UuidMissingNameValue-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingNameValue-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/UuidMissingNameValue-stderr.txt b/Tests/RunCMake/string/UuidMissingNameValue-stderr.txt new file mode 100644 index 000000000..0b7cde463 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingNameValue-stderr.txt @@ -0,0 +1,4 @@ +CMake Error at UuidMissingNameValue.cmake:3 \(string\): + string UUID sub-command, NAME requires a value. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/string/UuidMissingNameValue.cmake b/Tests/RunCMake/string/UuidMissingNameValue.cmake new file mode 100644 index 000000000..407a1d7f8 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingNameValue.cmake @@ -0,0 +1,4 @@ +set(UUID_DNS_NAMESPACE 6ba7b810-9dad-11d1-80b4-00c04fd430c8) + +string(UUID WWW_EXAMPLE_COM_MD5_UUID + NAMESPACE ${UUID_DNS_NAMESPACE} TYPE MD5 NAME) diff --git a/Tests/RunCMake/string/UuidMissingNamespace-result.txt b/Tests/RunCMake/string/UuidMissingNamespace-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingNamespace-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/UuidMissingNamespace-stderr.txt b/Tests/RunCMake/string/UuidMissingNamespace-stderr.txt new file mode 100644 index 000000000..dfcfe42b2 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingNamespace-stderr.txt @@ -0,0 +1,4 @@ +CMake Error at UuidMissingNamespace.cmake:3 \(string\): + string UUID sub-command, malformed NAMESPACE UUID. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/string/UuidMissingNamespace.cmake b/Tests/RunCMake/string/UuidMissingNamespace.cmake new file mode 100644 index 000000000..5a71e4f70 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingNamespace.cmake @@ -0,0 +1,4 @@ +set(UUID_DNS_NAMESPACE 6ba7b810-9dad-11d1-80b4-00c04fd430c8) + +string(UUID WWW_EXAMPLE_COM_MD5_UUID + NAME www.example.com TYPE MD5) diff --git a/Tests/RunCMake/string/UuidMissingNamespaceValue-result.txt b/Tests/RunCMake/string/UuidMissingNamespaceValue-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingNamespaceValue-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/UuidMissingNamespaceValue-stderr.txt b/Tests/RunCMake/string/UuidMissingNamespaceValue-stderr.txt new file mode 100644 index 000000000..86585ad40 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingNamespaceValue-stderr.txt @@ -0,0 +1,4 @@ +CMake Error at UuidMissingNamespaceValue.cmake:3 \(string\): + string UUID sub-command, NAMESPACE requires a value. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/string/UuidMissingNamespaceValue.cmake b/Tests/RunCMake/string/UuidMissingNamespaceValue.cmake new file mode 100644 index 000000000..f2219c0ff --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingNamespaceValue.cmake @@ -0,0 +1,4 @@ +set(UUID_DNS_NAMESPACE 6ba7b810-9dad-11d1-80b4-00c04fd430c8) + +string(UUID WWW_EXAMPLE_COM_MD5_UUID + NAME www.example.com TYPE MD5 NAMESPACE) diff --git a/Tests/RunCMake/string/UuidMissingTypeValue-result.txt b/Tests/RunCMake/string/UuidMissingTypeValue-result.txt new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingTypeValue-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/string/UuidMissingTypeValue-stderr.txt b/Tests/RunCMake/string/UuidMissingTypeValue-stderr.txt new file mode 100644 index 000000000..70252f8cd --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingTypeValue-stderr.txt @@ -0,0 +1,4 @@ +CMake Error at UuidMissingTypeValue.cmake:3 \(string\): + string UUID sub-command, TYPE requires a value. +Call Stack \(most recent call first\): + CMakeLists.txt:3 \(include\) diff --git a/Tests/RunCMake/string/UuidMissingTypeValue.cmake b/Tests/RunCMake/string/UuidMissingTypeValue.cmake new file mode 100644 index 000000000..6678a4658 --- /dev/null +++ b/Tests/RunCMake/string/UuidMissingTypeValue.cmake @@ -0,0 +1,4 @@ +set(UUID_DNS_NAMESPACE 6ba7b810-9dad-11d1-80b4-00c04fd430c8) + +string(UUID WWW_EXAMPLE_COM_MD5_UUID + NAMESPACE ${UUID_DNS_NAMESPACE} NAME www.example.com TYPE)