On 11-Nov-14 21:28, Brad King wrote:
* All headers and sources need to include cmStandardIncludes.h first,
   directly or indirectly through other headers.  It can affect the
   way certain standard library headers are preprocessed on some
   platforms, and needs to be consistent across all translation units.

* Please add the .cxx files to 'bootstrap' and Source/CMakeLists.txt
   rather than including them in cmBootstrapCommands*.cxx.

* The windows header should be included in lower case <windows.h>.

* The cerrno and cstdio headers need to be just errno.h and stdio.h
   to work on some of the older compilers.

* Please add documentation to Help/command/file.rst and anywhere else
   that is appropriate.
Done

* Please add test cases for all APIs and error cases, probably in
   Tests/RunCMake/file using the RunCMake infrastructure.
I added test LOCK.cmake but not quite understand how to execute it. Is it possible to run tests only from 'Tests/RunCMake/file' directory?

By the way I got the following error (Visual Studio 2013) if I include 'cmFileLockResult.h' before 'cm_curl.h' in file 'cmFileCommand.cxx':

C:\Program Files (x86)\Windows Kits\8.1\Include\shared\ws2ipdef.h(202): error C2079: '_SOCKADDR_INET::Ipv4' uses undefined struct 'sockaddr_in' C:\Program Files (x86)\Windows Kits\8.1\Include\shared\ws2ipdef.h(715): error C2011: 'ip_mreq' : 'struct' type redefinition C:\Program Files (x86)\Windows Kits\8.1\Include\um\winsock.h(355) : see declaration of 'ip_mreq' C:\Program Files (x86)\Windows Kits\8.1\Include\um\ws2tcpip.h(696): error C3861: 'WSASetLastError': identifier not found C:\Program Files (x86)\Windows Kits\8.1\Include\um\ws2tcpip.h(703): error C3861: 'WSASetLastError': identifier not found C:\Program Files (x86)\Windows Kits\8.1\Include\um\ws2tcpip.h(742): error C3861: 'WSASetLastError': identifier not found C:\Program Files (x86)\Windows Kits\8.1\Include\um\ws2tcpip.h(749): error C3861: 'WSASetLastError': identifier not found C:\Program Files (x86)\Windows Kits\8.1\Include\um\ws2tcpip.h(793): error C3861: 'WSASetLastError': identifier not found C:\Program Files (x86)\Windows Kits\8.1\Include\um\ws2tcpip.h(800): error C3861: 'WSASetLastError': identifier not found C:\Program Files (x86)\Windows Kits\8.1\Include\um\ws2tcpip.h(841): error C3861: 'WSASetLastError': identifier not found C:\Program Files (x86)\Windows Kits\8.1\Include\um\ws2tcpip.h(848): error C3861: 'WSASetLastError': identifier not found ...\cmake\Utilities\cmcurl/include/curl/curl.h(337): error C2079: 'curl_sockaddr::addr' uses undefined struct 'sockaddr'

Compiles fine if 'cmFileLockResult.h' placed after 'cm_curl.h'.

Ruslo
>From 55514802c135595e658f1897c123c0daf291ae81 Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_bara...@yahoo.com>
Date: Thu, 13 Nov 2014 17:35:36 +0400
Subject: [PATCH 1/5] Add class cmFileLockResult

---
 Source/cmFileLockResult.cxx | 111 ++++++++++++++++++++++++++++++++++++++++++++
 Source/cmFileLockResult.h   |  85 +++++++++++++++++++++++++++++++++
 2 files changed, 196 insertions(+)
 create mode 100644 Source/cmFileLockResult.cxx
 create mode 100644 Source/cmFileLockResult.h

diff --git a/Source/cmFileLockResult.cxx b/Source/cmFileLockResult.cxx
new file mode 100644
index 0000000..045e7ee
--- /dev/null
+++ b/Source/cmFileLockResult.cxx
@@ -0,0 +1,111 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2014 Ruslan Baratov
+
+  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 "cmFileLockResult.h"
+
+#include <errno.h>
+
+cmFileLockResult cmFileLockResult::MakeOk()
+{
+  return cmFileLockResult(OK, 0);
+}
+
+cmFileLockResult cmFileLockResult::MakeSystem()
+{
+#if defined(_WIN32)
+  const Error lastError = GetLastError();
+#else
+  const Error lastError = errno;
+#endif
+  return cmFileLockResult(SYSTEM, lastError);
+}
+
+cmFileLockResult cmFileLockResult::MakeTimeout()
+{
+  return cmFileLockResult(TIMEOUT, 0);
+}
+
+cmFileLockResult cmFileLockResult::MakeAlreadyLocked()
+{
+  return cmFileLockResult(ALREADY_LOCKED, 0);
+}
+
+cmFileLockResult cmFileLockResult::MakeInternal()
+{
+  return cmFileLockResult(INTERNAL, 0);
+}
+
+cmFileLockResult cmFileLockResult::MakeNoFunction()
+{
+  return cmFileLockResult(NO_FUNCTION, 0);
+}
+
+bool cmFileLockResult::IsOk() const
+{
+  return this->Type == OK;
+}
+
+std::string cmFileLockResult::GetOutputMessage() const
+{
+  switch (this->Type)
+    {
+    case OK:
+      return "0";
+    case SYSTEM:
+#if defined(_WIN32)
+      {
+      char* errorText = NULL;
+
+      // http://stackoverflow.com/a/455533/2288008
+      DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM |
+          FORMAT_MESSAGE_ALLOCATE_BUFFER |
+          FORMAT_MESSAGE_IGNORE_INSERTS;
+      ::FormatMessageA(
+          flags,
+          NULL,
+          this->ErrorValue,
+          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+          (LPSTR)&errorText,
+          0,
+          NULL
+      );
+
+      if (errorText != NULL)
+        {
+        const std::string message = errorText;
+        ::LocalFree(errorText);
+        return message;
+        }
+      else
+        {
+        return "Internal error (FormatMessageA failed)";
+        }
+      }
+#else
+      return strerror(this->ErrorValue);
+#endif
+    case TIMEOUT:
+      return "Timeout reached";
+    case ALREADY_LOCKED:
+      return "File already locked";
+    case NO_FUNCTION:
+      return "'GUARD FUNCTION' not used in function definition";
+    case INTERNAL:
+    default:
+      return "Internal error";
+    }
+}
+
+cmFileLockResult::cmFileLockResult(ErrorType typeValue, Error errorValue):
+    Type(typeValue), ErrorValue(errorValue)
+{
+}
diff --git a/Source/cmFileLockResult.h b/Source/cmFileLockResult.h
new file mode 100644
index 0000000..531fb49
--- /dev/null
+++ b/Source/cmFileLockResult.h
@@ -0,0 +1,85 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2014 Ruslan Baratov
+
+  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 cmFileLockResult_h
+#define cmFileLockResult_h
+
+#include "cmStandardIncludes.h"
+
+#if defined(_WIN32)
+# include <windows.h> // DWORD
+#endif
+
+/**
+  * @brief Result of the locking/unlocking file.
+  * @note See @c cmFileLock
+  */
+class cmFileLockResult
+{
+ public:
+#if defined(_WIN32)
+  typedef DWORD Error;
+#else
+  typedef int Error;
+#endif
+
+  /**
+    * @brief Successful lock/unlock.
+    */
+  static cmFileLockResult MakeOk();
+
+  /**
+    * @brief Lock/Unlock failed. Read error/GetLastError.
+    */
+  static cmFileLockResult MakeSystem();
+
+  /**
+    * @brief Lock/Unlock failed. Timeout reached.
+    */
+  static cmFileLockResult MakeTimeout();
+
+  /**
+    * @brief File already locked.
+    */
+  static cmFileLockResult MakeAlreadyLocked();
+
+  /**
+    * @brief Internal error.
+    */
+  static cmFileLockResult MakeInternal();
+
+  /**
+    * @brief Try to lock with function guard outside of the function
+    */
+  static cmFileLockResult MakeNoFunction();
+
+  bool IsOk() const;
+  std::string GetOutputMessage() const;
+
+ private:
+  enum ErrorType
+  {
+    OK,
+    SYSTEM,
+    TIMEOUT,
+    ALREADY_LOCKED,
+    INTERNAL,
+    NO_FUNCTION
+  };
+
+  cmFileLockResult(ErrorType type, Error errorValue);
+
+  ErrorType Type;
+  Error ErrorValue;
+};
+
+#endif // cmFileLockResult_h
-- 
2.1.1

>From 29727385af600c6aef0771c2bc835e4939cf7e02 Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_bara...@yahoo.com>
Date: Thu, 13 Nov 2014 17:36:02 +0400
Subject: [PATCH 2/5] Add class cmFileLock

---
 Source/cmFileLock.cxx      |  77 +++++++++++++++++++++++++++
 Source/cmFileLock.h        |  74 ++++++++++++++++++++++++++
 Source/cmFileLockUnix.cxx  | 102 ++++++++++++++++++++++++++++++++++++
 Source/cmFileLockWin32.cxx | 126 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 379 insertions(+)
 create mode 100755 Source/cmFileLock.cxx
 create mode 100644 Source/cmFileLock.h
 create mode 100755 Source/cmFileLockUnix.cxx
 create mode 100755 Source/cmFileLockWin32.cxx

diff --git a/Source/cmFileLock.cxx b/Source/cmFileLock.cxx
new file mode 100755
index 0000000..f69c36a
--- /dev/null
+++ b/Source/cmFileLock.cxx
@@ -0,0 +1,77 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2014 Ruslan Baratov
+
+  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 "cmFileLock.h"
+
+#include <assert.h>
+#include "cmFileLockResult.h"
+
+// Common implementation
+
+cmFileLock::~cmFileLock()
+{
+  if (!this->Filename.empty())
+    {
+    const cmFileLockResult result = this->Release();
+    assert(result.IsOk());
+    }
+}
+
+cmFileLockResult cmFileLock::Lock(
+    const std::string& filename, unsigned timeout)
+{
+  if (filename.empty())
+    {
+    // Error is internal since all the directories and file must be created
+    // before actual lock called.
+    return cmFileLockResult::MakeInternal();
+    }
+
+  if (!this->Filename.empty())
+    {
+    // Error is internal since double-lock must be checked in class
+    // cmFileLockPool by the cmFileLock::IsLocked method.
+    return cmFileLockResult::MakeInternal();
+    }
+
+  this->Filename = filename;
+  cmFileLockResult result = this->OpenFile();
+  if (result.IsOk())
+    {
+    if (timeout == static_cast<unsigned>(-1))
+      {
+      result = this->LockWithoutTimeout();
+      }
+    else
+      {
+      result = this->LockWithTimeout(timeout);
+      }
+    }
+
+  if (!result.IsOk())
+    {
+    this->Filename = "";
+    }
+
+  return result;
+}
+
+bool cmFileLock::IsLocked(const std::string& filename) const
+{
+  return filename == this->Filename;
+}
+
+#if defined(_WIN32)
+# include "cmFileLockWin32.cxx"
+#else
+# include "cmFileLockUnix.cxx"
+#endif
diff --git a/Source/cmFileLock.h b/Source/cmFileLock.h
new file mode 100644
index 0000000..7c2803b
--- /dev/null
+++ b/Source/cmFileLock.h
@@ -0,0 +1,74 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2014 Ruslan Baratov
+
+  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 cmFileLock_h
+#define cmFileLock_h
+
+#include "cmStandardIncludes.h"
+
+#if defined(_WIN32)
+# include <windows.h> // HANDLE
+#endif
+
+class cmFileLockResult;
+
+/**
+  * @brief Cross-platform file locking.
+  * @detail Under the hood this class use 'fcntl' for Unix-like platforms and
+  * 'LockFileEx'/'UnlockFileEx' for Win32 platform. Locks are exclusive and
+  * advisory.
+  */
+class cmFileLock
+{
+ public:
+  cmFileLock();
+  ~cmFileLock();
+
+  /**
+    * @brief Lock the file.
+    * @param timeoutSec Lock timeout. If -1 try until success or fatal error.
+    */
+  cmFileLockResult Lock(const std::string& filename, unsigned timeoutSec);
+
+  /**
+    * @brief Unlock the file.
+    */
+  cmFileLockResult Release();
+
+  /**
+    * @brief Check file is locked by this class.
+    * @detail This function helps to find double locks (deadlocks) and to do
+    * explicit unlocks.
+    */
+  bool IsLocked(const std::string& filename) const;
+
+ private:
+  cmFileLock(const cmFileLock&);
+  cmFileLock& operator=(const cmFileLock&);
+
+  cmFileLockResult OpenFile();
+  cmFileLockResult LockWithoutTimeout();
+  cmFileLockResult LockWithTimeout(unsigned timeoutSec);
+
+#if defined(_WIN32)
+  typedef HANDLE FileId;
+  BOOL LockFile(DWORD flags);
+#else
+  typedef int FileId;
+  int LockFile(int cmd, int type);
+#endif
+
+  FileId File;
+  std::string Filename;
+};
+
+#endif // cmFileLock_h
diff --git a/Source/cmFileLockUnix.cxx b/Source/cmFileLockUnix.cxx
new file mode 100755
index 0000000..519df3e
--- /dev/null
+++ b/Source/cmFileLockUnix.cxx
@@ -0,0 +1,102 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2014 Ruslan Baratov
+
+  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 "cmFileLock.h"
+
+#include <errno.h> // errno
+#include <stdio.h> // SEEK_SET
+#include <fcntl.h>
+#include <unistd.h> // sleep
+
+cmFileLock::cmFileLock(): File(-1)
+{
+}
+
+cmFileLockResult cmFileLock::Release()
+{
+  if (this->Filename.empty())
+    {
+    return cmFileLockResult::MakeOk();
+    }
+  const int lockResult = this->LockFile(F_SETLK, F_UNLCK);
+
+  this->Filename = "";
+
+  if (lockResult == 0)
+    {
+    return cmFileLockResult::MakeOk();
+    }
+  else
+    {
+    return cmFileLockResult::MakeSystem();
+    }
+}
+
+cmFileLockResult cmFileLock::OpenFile()
+{
+  this->File = ::open(this->Filename.c_str(), O_RDWR);
+  if (this->File == -1)
+    {
+    return cmFileLockResult::MakeSystem();
+    }
+  else
+    {
+    return cmFileLockResult::MakeOk();
+    }
+}
+
+cmFileLockResult cmFileLock::LockWithoutTimeout()
+{
+  if (this->LockFile(F_SETLKW, F_WRLCK) == -1)
+    {
+    return cmFileLockResult::MakeSystem();
+    }
+  else
+    {
+    return cmFileLockResult::MakeOk();
+    }
+}
+
+cmFileLockResult cmFileLock::LockWithTimeout(unsigned seconds)
+{
+  while (true)
+    {
+    if (this->LockFile(F_SETLK, F_WRLCK) == -1)
+      {
+      if (errno != EAGAIN)
+        {
+        return cmFileLockResult::MakeSystem();
+        }
+      }
+    else
+      {
+      return cmFileLockResult::MakeOk();
+      }
+    if (seconds == 0)
+      {
+      return cmFileLockResult::MakeTimeout();
+      }
+    --seconds;
+    ::sleep(1);
+    }
+}
+
+int cmFileLock::LockFile(int cmd, int type)
+{
+  struct ::flock lock;
+  lock.l_start = 0;
+  lock.l_len = 0; // lock all bytes
+  lock.l_pid = 0; // unused (for F_GETLK only)
+  lock.l_type = type; // exclusive lock
+  lock.l_whence = SEEK_SET;
+  return ::fcntl(this->File, cmd, &lock);
+}
diff --git a/Source/cmFileLockWin32.cxx b/Source/cmFileLockWin32.cxx
new file mode 100755
index 0000000..d5b069d
--- /dev/null
+++ b/Source/cmFileLockWin32.cxx
@@ -0,0 +1,126 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2014 Ruslan Baratov
+
+  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 "cmFileLock.h"
+
+#include <windows.h> // CreateFile
+#include <cmsys/Encoding.hxx>
+
+cmFileLock::cmFileLock(): File(INVALID_HANDLE_VALUE)
+{
+}
+
+cmFileLockResult cmFileLock::Release()
+{
+  if (this->Filename.empty())
+    {
+    return cmFileLockResult::MakeOk();
+    }
+  const unsigned long len = static_cast<unsigned long>(-1);
+  static OVERLAPPED overlapped;
+  const DWORD reserved = 0;
+  const BOOL unlockResult = UnlockFileEx(
+      File,
+      reserved,
+      len,
+      len,
+      &overlapped
+  );
+
+  this->Filename = "";
+
+  if (unlockResult)
+    {
+    return cmFileLockResult::MakeOk();
+    }
+  else
+    {
+    return cmFileLockResult::MakeSystem();
+    }
+}
+
+cmFileLockResult cmFileLock::OpenFile()
+{
+  const DWORD access = GENERIC_READ | GENERIC_WRITE;
+  const DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+  const PSECURITY_ATTRIBUTES security = NULL;
+  const DWORD attr = 0;
+  const HANDLE templ = NULL;
+  this->File = CreateFile(
+      cmsys::Encoding::ToWide(this->Filename.c_str()).c_str(),
+      access,
+      shareMode,
+      security,
+      OPEN_EXISTING,
+      attr,
+      templ
+  );
+  if (this->File == INVALID_HANDLE_VALUE)
+    {
+    return cmFileLockResult::MakeSystem();
+    }
+  else
+    {
+    return cmFileLockResult::MakeOk();
+    }
+}
+
+cmFileLockResult cmFileLock::LockWithoutTimeout()
+{
+  if (!this->LockFile(LOCKFILE_EXCLUSIVE_LOCK))
+    {
+    return cmFileLockResult::MakeSystem();
+    }
+  else
+    {
+    return cmFileLockResult::MakeOk();
+    }
+}
+
+cmFileLockResult cmFileLock::LockWithTimeout(unsigned seconds)
+{
+  const DWORD flags = LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY;
+  while (true)
+    {
+    const BOOL result = this->LockFile(flags);
+    if (result)
+      {
+      return cmFileLockResult::MakeOk();
+      }
+    const DWORD error = GetLastError();
+    if (error != ERROR_LOCK_VIOLATION)
+      {
+      return cmFileLockResult::MakeSystem();
+      }
+    if (seconds == 0)
+      {
+      return cmFileLockResult::MakeTimeout();
+      }
+    --seconds;
+    ::Sleep(1000);
+    }
+}
+
+BOOL cmFileLock::LockFile(DWORD flags)
+{
+  const DWORD reserved = 0;
+  const unsigned long len = static_cast<unsigned long>(-1);
+  static OVERLAPPED overlapped;
+  return LockFileEx(
+      this->File,
+      flags,
+      reserved,
+      len,
+      len,
+      &overlapped
+  );
+}
-- 
2.1.1

>From be8c24118e06d66fe59950792af194d10e0c3f25 Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_bara...@yahoo.com>
Date: Thu, 13 Nov 2014 17:36:25 +0400
Subject: [PATCH 3/5] Add class cmFileLockPool

---
 Source/cmFileLockPool.cxx | 198 ++++++++++++++++++++++++++++++++++++++++++++++
 Source/cmFileLockPool.h   | 100 +++++++++++++++++++++++
 2 files changed, 298 insertions(+)
 create mode 100755 Source/cmFileLockPool.cxx
 create mode 100644 Source/cmFileLockPool.h

diff --git a/Source/cmFileLockPool.cxx b/Source/cmFileLockPool.cxx
new file mode 100755
index 0000000..e84e71a
--- /dev/null
+++ b/Source/cmFileLockPool.cxx
@@ -0,0 +1,198 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2014 Ruslan Baratov
+
+  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 "cmFileLockPool.h"
+
+#include <assert.h>
+
+#include "cmFileLock.h"
+#include "cmFileLockResult.h"
+
+cmFileLockPool::cmFileLockPool()
+{
+}
+
+cmFileLockPool::~cmFileLockPool()
+{
+  for (It i = this->FunctionScopes.begin();
+      i != this->FunctionScopes.end(); ++i)
+    {
+    delete *i;
+    }
+
+  for (It i = this->FileScopes.begin(); i != this->FileScopes.end(); ++i)
+    {
+    delete *i;
+    }
+}
+
+void cmFileLockPool::PushFunctionScope()
+{
+  this->FunctionScopes.push_back(new ScopePool());
+}
+
+void cmFileLockPool::PopFunctionScope()
+{
+  assert(!this->FunctionScopes.empty());
+  delete this->FunctionScopes.back();
+  this->FunctionScopes.pop_back();
+}
+
+void cmFileLockPool::PushFileScope()
+{
+  this->FileScopes.push_back(new ScopePool());
+}
+
+void cmFileLockPool::PopFileScope()
+{
+  assert(!this->FileScopes.empty());
+  delete this->FileScopes.back();
+  this->FileScopes.pop_back();
+}
+
+cmFileLockResult cmFileLockPool::LockFunctionScope(
+    const std::string& filename, unsigned timeoutSec)
+{
+  if (this->IsAlreadyLocked(filename))
+    {
+    return cmFileLockResult::MakeAlreadyLocked();
+    }
+  if (this->FunctionScopes.empty())
+    {
+    return cmFileLockResult::MakeNoFunction();
+    }
+  return this->FunctionScopes.back()->Lock(filename, timeoutSec);
+}
+
+cmFileLockResult cmFileLockPool::LockFileScope(
+    const std::string& filename, unsigned timeoutSec)
+{
+  if (this->IsAlreadyLocked(filename))
+    {
+    return cmFileLockResult::MakeAlreadyLocked();
+    }
+  assert(!this->FileScopes.empty());
+  return this->FileScopes.back()->Lock(filename, timeoutSec);
+}
+
+cmFileLockResult cmFileLockPool::LockProcessScope(
+    const std::string& filename, unsigned timeoutSec)
+{
+  if (this->IsAlreadyLocked(filename))
+    {
+    return cmFileLockResult::MakeAlreadyLocked();
+    }
+  return this->ProcessScope.Lock(filename, timeoutSec);
+}
+
+cmFileLockResult cmFileLockPool::Release(const std::string& filename)
+{
+  for (It i = this->FunctionScopes.begin();
+      i != this->FunctionScopes.end(); ++i)
+    {
+    const cmFileLockResult result = (*i)->Release(filename);
+    if (!result.IsOk())
+      {
+      return result;
+      }
+    }
+
+  for (It i = this->FileScopes.begin(); i != this->FileScopes.end(); ++i)
+    {
+    const cmFileLockResult result = (*i)->Release(filename);
+    if (!result.IsOk())
+      {
+      return result;
+      }
+    }
+
+  return this->ProcessScope.Release(filename);
+}
+
+bool cmFileLockPool::IsAlreadyLocked(const std::string& filename) const
+{
+  for (CIt i = this->FunctionScopes.begin();
+      i != this->FunctionScopes.end(); ++i)
+    {
+    const bool result = (*i)->IsAlreadyLocked(filename);
+    if (result)
+      {
+      return true;
+      }
+    }
+
+  for (CIt i = this->FileScopes.begin(); i != this->FileScopes.end(); ++i)
+    {
+    const bool result = (*i)->IsAlreadyLocked(filename);
+    if (result)
+      {
+      return true;
+      }
+    }
+
+  return this->ProcessScope.IsAlreadyLocked(filename);
+}
+
+cmFileLockPool::ScopePool::ScopePool()
+{
+}
+
+cmFileLockPool::ScopePool::~ScopePool()
+{
+  for (It i = this->Locks.begin(); i != this->Locks.end(); ++i)
+    {
+    delete *i;
+    }
+}
+
+cmFileLockResult cmFileLockPool::ScopePool::Lock(
+    const std::string& filename, unsigned timeoutSec)
+{
+  cmFileLock *lock = new cmFileLock();
+  const cmFileLockResult result = lock->Lock(filename, timeoutSec);
+  if (result.IsOk())
+    {
+    this->Locks.push_back(lock);
+    return cmFileLockResult::MakeOk();
+    }
+  else
+    {
+    delete lock;
+    return result;
+    }
+}
+
+cmFileLockResult cmFileLockPool::ScopePool::Release(
+    const std::string& filename)
+{
+  for (It i = this->Locks.begin(); i != this->Locks.end(); ++i)
+    {
+    if ((*i)->IsLocked(filename))
+      {
+      return (*i)->Release();
+      }
+    }
+  return cmFileLockResult::MakeOk();
+}
+
+bool cmFileLockPool::ScopePool::IsAlreadyLocked(
+    const std::string& filename) const
+{
+  for (CIt i = this->Locks.begin(); i != this->Locks.end(); ++i)
+    {
+    if ((*i)->IsLocked(filename))
+      {
+      return true;
+      }
+    }
+  return false;
+}
diff --git a/Source/cmFileLockPool.h b/Source/cmFileLockPool.h
new file mode 100644
index 0000000..a63540c
--- /dev/null
+++ b/Source/cmFileLockPool.h
@@ -0,0 +1,100 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2014 Ruslan Baratov
+
+  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 cmFileLockPool_h
+#define cmFileLockPool_h
+
+#include "cmStandardIncludes.h"
+
+class cmFileLockResult;
+class cmFileLock;
+
+class cmFileLockPool
+{
+ public:
+  cmFileLockPool();
+  ~cmFileLockPool();
+
+  //@{
+  /**
+    * @brief Function scope control.
+    */
+  void PushFunctionScope();
+  void PopFunctionScope();
+  //@}
+
+  //@{
+  /**
+    * @brief File scope control.
+    */
+  void PushFileScope();
+  void PopFileScope();
+  //@}
+
+  //@{
+  /**
+    * @brief Lock the file in given scope.
+    * @param timeoutSec Lock timeout. If -1 try until success or fatal error.
+    */
+  cmFileLockResult LockFunctionScope(
+      const std::string& filename, unsigned timeoutSec
+  );
+  cmFileLockResult LockFileScope(
+      const std::string& filename, unsigned timeoutSec
+  );
+  cmFileLockResult LockProcessScope(
+      const std::string& filename, unsigned timeoutSec
+  );
+  //@}
+
+  /**
+    * @brief Unlock the file explicitly.
+    */
+  cmFileLockResult Release(const std::string& filename);
+
+ private:
+  cmFileLockPool(const cmFileLockPool&);
+  cmFileLockPool& operator=(const cmFileLockPool&);
+
+  bool IsAlreadyLocked(const std::string& filename) const;
+
+  class ScopePool
+  {
+   public:
+    ScopePool();
+    ~ScopePool();
+
+    cmFileLockResult Lock(const std::string& filename, unsigned timeoutSec);
+    cmFileLockResult Release(const std::string& filename);
+    bool IsAlreadyLocked(const std::string& filename) const;
+
+   private:
+    ScopePool(const ScopePool&);
+    ScopePool& operator=(const ScopePool&);
+
+    typedef std::list<cmFileLock*> List;
+    typedef List::iterator It;
+    typedef List::const_iterator CIt;
+
+    List Locks;
+  };
+
+  typedef std::list<ScopePool*> List;
+
+  typedef List::iterator It;
+  typedef List::const_iterator CIt;
+
+  List FunctionScopes;
+  List FileScopes;
+  ScopePool ProcessScope;
+};
+
+#endif // cmFileLockPool_h
-- 
2.1.1

>From fc43d35350334613e9afbea1581cc90b1547fa5a Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_bara...@yahoo.com>
Date: Thu, 13 Nov 2014 17:39:13 +0400
Subject: [PATCH 4/5] New command 'file(LOCK ...)'

---
 Source/CMakeLists.txt       |   6 ++
 Source/cmFileCommand.cxx    | 192 ++++++++++++++++++++++++++++++++++++++++++++
 Source/cmFileCommand.h      |   1 +
 Source/cmGlobalGenerator.h  |   6 ++
 Source/cmLocalGenerator.cxx |   2 +
 Source/cmMakefile.cxx       |   4 +
 bootstrap                   |   3 +
 7 files changed, 214 insertions(+)

diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 7705683..a4c982f 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -219,6 +219,12 @@ set(SRCS
   cmExtraKateGenerator.h
   cmExtraSublimeTextGenerator.cxx
   cmExtraSublimeTextGenerator.h
+  cmFileLock.cxx
+  cmFileLock.h
+  cmFileLockPool.cxx
+  cmFileLockPool.h
+  cmFileLockResult.cxx
+  cmFileLockResult.h
   cmFileTimeComparison.cxx
   cmFileTimeComparison.h
   cmGeneratedFileStream.cxx
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index b0ddff4..3b1f44e 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -23,6 +23,8 @@
 #include "cm_curl.h"
 #endif
 
+#include "cmFileLockResult.h"
+
 #undef GetCurrentDirectory
 #include <assert.h>
 #include <sys/types.h>
@@ -202,6 +204,10 @@ bool cmFileCommand
     {
     return this->HandleGenerateCommand(args);
     }
+  else if ( subCommand == "LOCK" )
+    {
+    return this->HandleLockCommand(args);
+    }
 
   std::string e = "does not recognize sub-command "+subCommand;
   this->SetError(e);
@@ -3502,6 +3508,192 @@ bool cmFileCommand::HandleGenerateCommand(
 }
 
 //----------------------------------------------------------------------------
+bool cmFileCommand::HandleLockCommand(
+  std::vector<std::string> const& args)
+{
+  // Default values
+  bool directory = false;
+  bool release = false;
+  enum Guard {
+    GUARD_FUNCTION,
+    GUARD_FILE,
+    GUARD_PROCESS
+  };
+  Guard guard = GUARD_PROCESS;
+  std::string resultVariable;
+  unsigned timeout = static_cast<unsigned>(-1);
+
+  // Parse arguments
+  if(args.size() < 2)
+    {
+    this->SetError("sub-command LOCK requires at least two arguments.");
+    return false;
+    }
+
+  std::string path = args[1];
+  for (unsigned i = 2; i < args.size(); ++i)
+    {
+    if (args[i] == "DIRECTORY")
+      {
+      directory = true;
+      }
+    else if (args[i] == "RELEASE")
+      {
+      release = true;
+      }
+    else if (args[i] == "GUARD")
+      {
+      ++i;
+      const char* merr = ": expected FUNCTION, FILE or PROCESS after GUARD";
+      if (i >= args.size())
+        {
+        this->SetError(merr);
+        return false;
+        }
+      else
+        {
+        if (args[i] == "FUNCTION")
+          {
+          guard = GUARD_FUNCTION;
+          }
+        else if (args[i] == "FILE")
+          {
+          guard = GUARD_FILE;
+          }
+        else if (args[i] == "PROCESS")
+          {
+          guard = GUARD_PROCESS;
+          }
+        else
+          {
+          cmOStringStream e;
+          e << merr << ", but got: \"" << args[i] << "\".";
+          this->SetError(e.str());
+          return false;
+          }
+        }
+      }
+    else if (args[i] == "RESULT_VARIABLE")
+      {
+      ++i;
+      if (i >= args.size())
+        {
+        this->SetError(": expected variable name after RESULT_VARIABLE");
+        return false;
+        }
+      resultVariable = args[i];
+      }
+    else if (args[i] == "TIMEOUT")
+      {
+      ++i;
+      if (i >= args.size())
+        {
+        this->SetError(": expected timeout value after TIMEOUT");
+        return false;
+        }
+      int scanned;
+      if(sscanf(args[i].c_str(), "%d", &scanned) != 1 || scanned < 0)
+        {
+        cmOStringStream e;
+        e << "TIMEOUT value \"" << args[i] << "\" is not an unsigned integer.";
+        this->SetError(e.str());
+        return false;
+        }
+      timeout = static_cast<unsigned>(scanned);
+      }
+    else
+      {
+      cmOStringStream e;
+      e << ": expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or TIMEOUT";
+      e << ", but got: \"" << args[i] << "\".";
+      this->SetError(e.str());
+      return false;
+      }
+    }
+
+  if (directory)
+    {
+    path += "/cmake.lock";
+    }
+
+  if (!cmsys::SystemTools::FileIsFullPath(path))
+    {
+    path = this->Makefile->GetCurrentDirectory() + ("/" + path);
+    }
+
+  // Unify path (remove '//', '/../', ...)
+  path = cmSystemTools::CollapseFullPath(path);
+
+  // Create file and directories if needed
+  std::string parentDir = cmSystemTools::GetParentDirectory(path);
+  if (!cmSystemTools::MakeDirectory(parentDir))
+    {
+    cmOStringStream e;
+    e << ": directory \"" << parentDir << "\" creation failed ";
+    e << "(check permissions).";
+    this->SetError(e.str());
+    cmSystemTools::SetFatalErrorOccured();
+    return false;
+    }
+  cmsys::ofstream file(path.c_str());
+  if (!file.good())
+    {
+    cmOStringStream e;
+    e << ": file \"" << path << "\" creation failed (check permissions).";
+    this->SetError(e.str());
+    cmSystemTools::SetFatalErrorOccured();
+    return false;
+    }
+  file.close();
+
+  // Actual lock/unlock
+  cmFileLockPool& lockPool = this->Makefile->GetLocalGenerator()->
+      GetGlobalGenerator()->GetFileLockPool();
+
+  cmFileLockResult fileLockResult(cmFileLockResult::MakeOk());
+  if (release)
+    {
+    fileLockResult = lockPool.Release(path);
+    }
+  else
+    {
+    switch (guard)
+      {
+      case GUARD_FUNCTION:
+        fileLockResult = lockPool.LockFunctionScope(path, timeout);
+        break;
+      case GUARD_FILE:
+        fileLockResult = lockPool.LockFileScope(path, timeout);
+        break;
+      case GUARD_PROCESS:
+        fileLockResult = lockPool.LockProcessScope(path, timeout);
+        break;
+      default:
+        cmSystemTools::SetFatalErrorOccured();
+        return false;
+      }
+    }
+
+  const std::string result = fileLockResult.GetOutputMessage();
+
+  if (resultVariable.empty() && !fileLockResult.IsOk())
+    {
+    cmOStringStream e;
+    e << ": error locking file \"" << path << "\" (" << result << ").";
+    this->SetError(e.str());
+    cmSystemTools::SetFatalErrorOccured();
+    return false;
+    }
+
+  if (!resultVariable.empty())
+    {
+    this->Makefile->AddDefinition(resultVariable, result.c_str());
+    }
+
+  return true;
+}
+
+//----------------------------------------------------------------------------
 bool cmFileCommand::HandleTimestampCommand(
   std::vector<std::string> const& args)
 {
diff --git a/Source/cmFileCommand.h b/Source/cmFileCommand.h
index 8d66fdf..a4d341f 100644
--- a/Source/cmFileCommand.h
+++ b/Source/cmFileCommand.h
@@ -75,6 +75,7 @@ protected:
 
   bool HandleTimestampCommand(std::vector<std::string> const& args);
   bool HandleGenerateCommand(std::vector<std::string> const& args);
+  bool HandleLockCommand(std::vector<std::string> const& args);
 
 private:
   void AddEvaluationFile(const std::string &inputName,
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index ddd7e91..00ba9a0 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -21,6 +21,7 @@
 #include "cmExportSetMap.h" // For cmExportSetMap
 #include "cmGeneratorTarget.h"
 #include "cmGeneratorExpression.h"
+#include "cmFileLockPool.h"
 
 #if defined(CMAKE_BUILD_WITH_CMAKE)
 # include <cmsys/hash_map.hxx>
@@ -341,6 +342,8 @@ public:
 
   bool GenerateCPackPropertiesFile();
 
+  cmFileLockPool& GetFileLockPool() { return FileLockPool; }
+
 protected:
   virtual void Generate();
 
@@ -488,6 +491,9 @@ private:
 
   // track targets to issue CMP0042 warning for.
   std::set<std::string> CMP0042WarnTargets;
+
+  // Pool of file locks
+  cmFileLockPool FileLockPool;
 };
 
 #endif
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 69b56c6..27cb8d0 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -79,9 +79,11 @@ public:
     this->GG = lg->GetGlobalGenerator();
     this->LG = this->GG->GetCurrentLocalGenerator();
     this->GG->SetCurrentLocalGenerator(lg);
+    this->GG->GetFileLockPool().PushFileScope();
     }
   ~cmLocalGeneratorCurrent()
     {
+    this->GG->GetFileLockPool().PopFileScope();
     this->GG->SetCurrentLocalGenerator(this->LG);
     }
 };
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 8a8aadc..a94c752 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -4426,6 +4426,8 @@ void cmMakefile::PushScope()
   this->Internal->VarStack.push(cmDefinitions(parent));
   this->Internal->VarInitStack.push(init);
   this->Internal->VarUsageStack.push(usage);
+  this->GetLocalGenerator()->GetGlobalGenerator()->
+    GetFileLockPool().PushFunctionScope();
 }
 
 void cmMakefile::PopScope()
@@ -4463,6 +4465,8 @@ void cmMakefile::PopScope()
     {
     this->Internal->VarUsageStack.top().insert(*it);
     }
+  this->GetLocalGenerator()->GetGlobalGenerator()->
+    GetFileLockPool().PopFunctionScope();
 }
 
 void cmMakefile::RaiseScope(const std::string& var, const char *varDef)
diff --git a/bootstrap b/bootstrap
index 94bed0e..19189d3 100755
--- a/bootstrap
+++ b/bootstrap
@@ -286,6 +286,9 @@ CMAKE_CXX_SOURCES="\
   cmSystemTools \
   cmTestGenerator \
   cmVersion \
+  cmFileLock \
+  cmFileLockPool \
+  cmFileLockResult \
   cmFileTimeComparison \
   cmGlobalUnixMakefileGenerator3 \
   cmLocalUnixMakefileGenerator3 \
-- 
2.1.1

>From 81b304fe8b87422733e9f91612b188583e7ea3db Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_bara...@yahoo.com>
Date: Thu, 13 Nov 2014 17:40:26 +0400
Subject: [PATCH 5/5] Update documentation of command file (LOCK option)

---
 Help/command/file.rst | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/Help/command/file.rst b/Help/command/file.rst
index dbc4149..600464e 100644
--- a/Help/command/file.rst
+++ b/Help/command/file.rst
@@ -305,3 +305,33 @@ status messages (subject to the 
:variable:`CMAKE_INSTALL_MESSAGE` variable),
 and ``NO_SOURCE_PERMISSIONS`` is default.
 Installation scripts generated by the :command:`install` command
 use this signature (with some undocumented options for internal use).
+
+------------------------------------------------------------------------------
+
+::
+
+  file(LOCK <path> [DIRECTORY] [RELEASE]
+       [GUARD <FUNCTION|FILE|PROCESS>]
+       [RESULT_VARIABLE <variable>]
+       [TIMEOUT <seconds>])
+
+Lock a file specified by ``<path>`` if no ``DIRECTORY`` option present and file
+``<path>/cmake.lock`` otherwise. File will be locked for scope defined by
+``GUARD`` option (default value is ``PROCESS``). ``RELEASE`` option can be used
+to unlock file explicitly. If option ``TIMEOUT`` is not specified CMake will
+wait until lock succeed or until fatal error occurs. If ``TIMEOUT`` is set to
+``0`` lock will be tried once and result will be reported immediately. If
+``TIMEOUT`` is not ``0`` CMake will try to lock file for the period specified
+by ``<seconds>`` value. Any errors will be interpreted as fatal if there is no
+``RESULT_VARIABLE`` option. Otherwise result will be stored in ``<variable>``
+and will be ``0`` on success or error message on failure.
+
+Note that lock is advisory - there is no guarantee that other processes will
+respect this lock, i.e. lock synchronize two or more CMake instances sharing
+some modifiable resources. Similar logic applied to ``DIRECTORY`` option -
+locking parent directory doesn't prevent other ``LOCK`` commands to lock any
+child directory or file.
+
+Trying to lock file twice is not allowed.  Any intermediate directories and
+file itself will be created if they not exist.  ``GUARD`` and ``TIMEOUT``
+options ignored on ``RELEASE`` operation.
-- 
2.1.1

-- 

Powered by www.kitware.com

Please keep messages on-topic and check the CMake FAQ at: 
http://www.cmake.org/Wiki/CMake_FAQ

Kitware offers various services to support the CMake community. For more 
information on each offering, please visit:

CMake Support: http://cmake.org/cmake/help/support.html
CMake Consulting: http://cmake.org/cmake/help/consulting.html
CMake Training Courses: http://cmake.org/cmake/help/training.html

Visit other Kitware open-source projects at 
http://www.kitware.com/opensource/opensource.html

Follow this link to subscribe/unsubscribe:
http://public.kitware.com/mailman/listinfo/cmake-developers

Reply via email to