Browse Source

StringUuid: Implement new string(UUID) sub-command.

Nils Gladitz 11 years ago
parent
commit
328e869433

+ 12 - 0
Help/command/string.rst

@@ -36,6 +36,8 @@ String operations.
   string(TIMESTAMP <output variable> [<format string>] [UTC])
   string(MAKE_C_IDENTIFIER <input string> <output variable>)
   string(GENEX_STRIP <input string> <output variable>)
+  string(UUID <output variable> NAMESPACE <namespace> NAME <name>
+         TYPE <MD5|SHA1> <UPPER>)
 
 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 <cmake-generator-expressions(7)>` 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 <namespace>
+(which itself has to be a valid UUID) and <name>.
+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.

+ 1 - 0
Source/CMakeLists.txt

@@ -308,6 +308,7 @@ set(SRCS
   cmTest.h
   cmTestGenerator.cxx
   cmTestGenerator.h
+  cmUuid.cxx
   cmVariableWatch.cxx
   cmVariableWatch.h
   cmVersion.cxx

+ 116 - 0
Source/cmStringCommand.cxx

@@ -20,6 +20,7 @@
 #include <time.h>
 
 #include <cmTimestamp.h>
+#include <cmUuid.h>
 
 //----------------------------------------------------------------------------
 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<std::string> 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<unsigned char> 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
+}

+ 1 - 0
Source/cmStringCommand.h

@@ -74,6 +74,7 @@ protected:
   bool HandleTimestampCommand(std::vector<std::string> const& args);
   bool HandleMakeCIdentifierCommand(std::vector<std::string> const& args);
   bool HandleGenexStripCommand(std::vector<std::string> const& args);
+  bool HandleUuidCommand(std::vector<std::string> const& args);
 
   class RegexReplacement
   {

+ 214 - 0
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 <string.h>
+
+#include <cmsys/MD5.h>
+#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<unsigned char> const& uuidNamespace,
+  std::string const& name) const
+{
+  std::vector<unsigned char> 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<unsigned char> const& uuidNamespace,
+    std::string const& name) const
+{
+  std::vector<unsigned char> 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<unsigned char> const& uuidNamespace,
+  std::string const& name, std::vector<unsigned char> &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<unsigned char> &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<unsigned char> &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;
+    }
+}

+ 55 - 0
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<unsigned char> const& uuidNamespace,
+    std::string const& name) const;
+
+  std::string FromSha1(std::vector<unsigned char> const& uuidNamespace,
+    std::string const& name) const;
+
+  bool StringToBinary(std::string const& input,
+    std::vector<unsigned char> &output) const;
+
+private:
+  std::string ByteToHex(unsigned char byte) const;
+
+  void CreateHashInput(std::vector<unsigned char> const& uuidNamespace,
+    std::string const& name, std::vector<unsigned char> &output) const;
+
+  std::string FromDigest(const unsigned char* digest,
+    unsigned char version) const;
+
+  bool StringToBinaryImpl(std::string const& input,
+    std::vector<unsigned char> &output) const;
+
+  std::string BinaryToString(const unsigned char* input) const;
+
+  bool IntFromHexDigit(char input, char& output) const;
+
+  std::vector<int> Groups;
+};
+
+
+#endif

+ 8 - 0
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)

+ 17 - 0
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()

+ 1 - 0
Tests/RunCMake/string/UuidBadNamespace-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
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\)

+ 4 - 0
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)

+ 1 - 0
Tests/RunCMake/string/UuidBadType-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
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\)

+ 4 - 0
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)

+ 1 - 0
Tests/RunCMake/string/UuidMissingNameValue-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
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\)

+ 4 - 0
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)

+ 1 - 0
Tests/RunCMake/string/UuidMissingNamespace-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
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\)

+ 4 - 0
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)

+ 1 - 0
Tests/RunCMake/string/UuidMissingNamespaceValue-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
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\)

+ 4 - 0
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)

+ 1 - 0
Tests/RunCMake/string/UuidMissingTypeValue-result.txt

@@ -0,0 +1 @@
+1

+ 4 - 0
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\)

+ 4 - 0
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)