[cmake-developers] New command 'file(LOCK_DIRECTORY ...)'
Ruslan Baratov
ruslan_baratov at yahoo.com
Thu Nov 13 09:01:05 EST 2014
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
-------------- next part --------------
>From 55514802c135595e658f1897c123c0daf291ae81 Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_baratov at 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
-------------- next part --------------
>From 29727385af600c6aef0771c2bc835e4939cf7e02 Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_baratov at 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
-------------- next part --------------
>From be8c24118e06d66fe59950792af194d10e0c3f25 Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_baratov at 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
-------------- next part --------------
>From fc43d35350334613e9afbea1581cc90b1547fa5a Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_baratov at 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
-------------- next part --------------
>From 81b304fe8b87422733e9f91612b188583e7ea3db Mon Sep 17 00:00:00 2001
From: Ruslan Baratov <ruslan_baratov at 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
More information about the cmake-developers
mailing list