aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoerg Bornemann <joerg.bornemann@digia.com>2014-07-11 17:19:04 +0200
committerChristian Kandeler <christian.kandeler@digia.com>2014-07-16 09:41:23 +0200
commit5ffdee71e50e9593794e66139baa0c355e0bd88e (patch)
tree2dcdf712bb546a04049c493f81c6fb93d21346ad
parente85876f236ad45b3327b7efefe79430d3972a4cf (diff)
Put a system-wide lock on the build directory.
So that two qbs instances can't overwrite each other's build graphs. In practice, this is probably most relevant for IDEs, as these hold a build graph for potentially long periods of time. Facilitates QLockFile that was introduced in Qt 5.1. For older Qt versions, no locking happens. Task-number: QBS-162 Change-Id: Ib598617fb1742eb57b6a017f40b9631d1d54e627 Reviewed-by: Joerg Bornemann <joerg.bornemann@digia.com>
-rw-r--r--src/lib/corelib/api/internaljobs.cpp10
-rw-r--r--src/lib/corelib/corelib.qbs2
-rw-r--r--src/lib/corelib/language/language.cpp5
-rw-r--r--src/lib/corelib/language/language.h4
-rw-r--r--src/lib/corelib/tools/buildgraphlocker.cpp96
-rw-r--r--src/lib/corelib/tools/buildgraphlocker.h63
-rw-r--r--src/lib/corelib/tools/tools.pri2
-rw-r--r--tests/auto/api/testdata/buildgraph-locking/project.qbs4
-rw-r--r--tests/auto/api/tst_api.cpp31
-rw-r--r--tests/auto/api/tst_api.h1
10 files changed, 215 insertions, 3 deletions
diff --git a/src/lib/corelib/api/internaljobs.cpp b/src/lib/corelib/api/internaljobs.cpp
index 840d7cc1c..ededa3279 100644
--- a/src/lib/corelib/api/internaljobs.cpp
+++ b/src/lib/corelib/api/internaljobs.cpp
@@ -42,6 +42,7 @@
#include <language/loader.h>
#include <logging/logger.h>
#include <logging/translator.h>
+#include <tools/buildgraphlocker.h>
#include <tools/error.h>
#include <tools/progressobserver.h>
#include <tools/preferences.h>
@@ -228,14 +229,23 @@ TopLevelProjectPtr InternalSetupProjectJob::project() const
void InternalSetupProjectJob::start()
{
+ BuildGraphLocker *bgLocker = 0;
try {
const ErrorInfo err = m_parameters.expandBuildConfiguration();
if (err.hasError())
throw err;
+ const QString projectId = TopLevelProject::deriveId(m_parameters.topLevelProfile(),
+ m_parameters.finalBuildConfigurationTree());
+ const QString buildDir
+ = TopLevelProject::deriveBuildDirectory(m_parameters.buildRoot(), projectId);
+ bgLocker = new BuildGraphLocker(ProjectBuildData::deriveBuildGraphFilePath(buildDir,
+ projectId));
execute();
+ m_project->bgLocker = bgLocker;
} catch (const ErrorInfo &error) {
m_project.clear();
setError(error);
+ delete bgLocker;
}
emit finished(this);
}
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 45dfa2079..ac121783b 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -278,6 +278,8 @@ QbsLibrary {
name: "tools"
prefix: name + '/'
files: [
+ "buildgraphlocker.cpp",
+ "buildgraphlocker.h",
"buildoptions.cpp",
"cleanoptions.cpp",
"codelocation.cpp",
diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp
index cc9d02180..d7815938e 100644
--- a/src/lib/corelib/language/language.cpp
+++ b/src/lib/corelib/language/language.cpp
@@ -39,6 +39,7 @@
#include <buildgraph/transformer.h>
#include <jsextensions/jsextensions.h>
#include <logging/translator.h>
+#include <tools/buildgraphlocker.h>
#include <tools/hostosinfo.h>
#include <tools/error.h>
#include <tools/propertyfinder.h>
@@ -937,12 +938,14 @@ void ResolvedProject::store(PersistentPool &pool) const
}
-TopLevelProject::TopLevelProject() : locked(false), lastResolveTime(FileTime::oldestTime())
+TopLevelProject::TopLevelProject()
+ : bgLocker(0), locked(false), lastResolveTime(FileTime::oldestTime())
{
}
TopLevelProject::~TopLevelProject()
{
+ delete bgLocker;
}
QString TopLevelProject::deriveId(const QString &profile, const QVariantMap &config)
diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h
index ed1b37320..e7df3232e 100644
--- a/src/lib/corelib/language/language.h
+++ b/src/lib/corelib/language/language.h
@@ -61,6 +61,7 @@ QT_END_NAMESPACE
namespace qbs {
namespace Internal {
+class BuildGraphLocker;
class BuildGraphLoader;
class BuildGraphVisitor;
@@ -460,7 +461,8 @@ public:
QHash<QString, bool> fileExistsResults; // Results of calls to "File.exists()".
QHash<QString, FileTime> fileLastModifiedResults; // Results of calls to "File.lastModified()".
QScopedPointer<ProjectBuildData> buildData;
- bool locked;
+ BuildGraphLocker *bgLocker; // This holds the system-wide build graph file lock.
+ bool locked; // This is the API-level lock for the project instance.
QSet<QString> buildSystemFiles;
FileTime lastResolveTime;
diff --git a/src/lib/corelib/tools/buildgraphlocker.cpp b/src/lib/corelib/tools/buildgraphlocker.cpp
new file mode 100644
index 000000000..f49686c8a
--- /dev/null
+++ b/src/lib/corelib/tools/buildgraphlocker.cpp
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Build Suite.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "buildgraphlocker.h"
+
+#include "error.h"
+
+#include <logging/translator.h>
+
+#include <QDir>
+#include <QFileInfo>
+#include <QString>
+
+namespace qbs {
+namespace Internal {
+
+BuildGraphLocker::BuildGraphLocker(const QString &buildGraphFilePath)
+#if HAS_QLOCKFILE
+ : m_lockFile(buildGraphFilePath + QLatin1String(".lock"))
+#endif
+{
+#if HAS_QLOCKFILE
+ const QString buildDir = QFileInfo(buildGraphFilePath).absolutePath();
+ if (!QDir::root().mkpath(buildDir))
+ throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': Failed to create directory."));
+ m_lockFile.setStaleLockTime(0);
+ int attemptsToGetInfo = 0;
+ do {
+ if (m_lockFile.tryLock(250))
+ return;
+ switch (m_lockFile.error()) {
+ case QLockFile::LockFailedError: {
+ qint64 pid;
+ QString hostName;
+ QString appName;
+ if (m_lockFile.getLockInfo(&pid, &hostName, &appName)) {
+ throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': "
+ "Already locked by '%2' (PID %3).")
+ .arg(buildGraphFilePath, appName).arg(pid));
+ }
+ break;
+ }
+ case QLockFile::PermissionError:
+ throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': Permission denied.")
+ .arg(buildGraphFilePath));
+ case QLockFile::UnknownError:
+ case QLockFile::NoError:
+ throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1' (reason unknown).")
+ .arg(buildGraphFilePath));
+ }
+ } while (++attemptsToGetInfo < 10);
+
+ // This very unlikely case arises if tryLock() repeatedly returns LockFailedError
+ // with the subsequent getLockInfo() failing as well.
+ throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1' (reason unknown).")
+ .arg(buildGraphFilePath));
+#else
+ Q_UNUSED(buildGraphFilePath);
+#endif
+}
+
+BuildGraphLocker::~BuildGraphLocker()
+{
+#if HAS_QLOCKFILE
+ m_lockFile.unlock();
+#endif
+}
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/tools/buildgraphlocker.h b/src/lib/corelib/tools/buildgraphlocker.h
new file mode 100644
index 000000000..d4b067789
--- /dev/null
+++ b/src/lib/corelib/tools/buildgraphlocker.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt Build Suite.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#ifndef QBS_BUILDGRAPHLOCKER_H
+#define QBS_BUILDGRAPHLOCKER_H
+
+#include <QtGlobal>
+
+#define HAS_QLOCKFILE (QT_VERSION >= QT_VERSION_CHECK(5, 1, 0))
+
+#if HAS_QLOCKFILE
+#include <QLockFile>
+#endif
+
+QT_BEGIN_NAMESPACE
+class QString;
+QT_END_NAMESPACE
+
+namespace qbs {
+namespace Internal {
+
+class BuildGraphLocker
+{
+public:
+ explicit BuildGraphLocker(const QString &buildGraphFilePath);
+ ~BuildGraphLocker();
+
+private:
+#if HAS_QLOCKFILE
+ QLockFile m_lockFile;
+#endif
+};
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // Include guard.
diff --git a/src/lib/corelib/tools/tools.pri b/src/lib/corelib/tools/tools.pri
index fca3d8f4b..38f8eb63d 100644
--- a/src/lib/corelib/tools/tools.pri
+++ b/src/lib/corelib/tools/tools.pri
@@ -1,6 +1,7 @@
INCLUDEPATH += $$PWD/../.. # for plugins
HEADERS += \
+ $$PWD/buildgraphlocker.h \
$$PWD/codelocation.h \
$$PWD/error.h \
$$PWD/fileinfo.h \
@@ -28,6 +29,7 @@ HEADERS += \
$$PWD/qttools.h
SOURCES += \
+ $$PWD/buildgraphlocker.cpp \
$$PWD/codelocation.cpp \
$$PWD/error.cpp \
$$PWD/fileinfo.cpp \
diff --git a/tests/auto/api/testdata/buildgraph-locking/project.qbs b/tests/auto/api/testdata/buildgraph-locking/project.qbs
new file mode 100644
index 000000000..e08b008bc
--- /dev/null
+++ b/tests/auto/api/testdata/buildgraph-locking/project.qbs
@@ -0,0 +1,4 @@
+import qbs
+
+Project {
+}
diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp
index 73e9bb1b6..db0fc4d41 100644
--- a/tests/auto/api/tst_api.cpp
+++ b/tests/auto/api/tst_api.cpp
@@ -120,6 +120,25 @@ void printProjectData(const qbs::ProjectData &project)
}
}
+
+void TestApi::buildGraphLocking()
+{
+ qbs::SetupProjectParameters setupParams = defaultSetupParameters();
+ const QString projectDirPath = QDir::cleanPath(m_workingDataDir + "/buildgraph-locking");
+ setupParams.setProjectFilePath(projectDirPath + "/project.qbs");
+ QScopedPointer<qbs::SetupProjectJob> setupJob(qbs::Project::setupProject(setupParams,
+ m_logSink, 0));
+ waitForFinished(setupJob.data());
+ QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString()));
+ const qbs::Project project = setupJob->project();
+ Q_UNUSED(project);
+ setupJob.reset(qbs::Project::setupProject(setupParams, m_logSink, 0));
+ waitForFinished(setupJob.data());
+ QVERIFY(setupJob->error().hasError());
+ QVERIFY2(setupJob->error().toString().contains("lock"),
+ qPrintable(setupJob->error().toString()));
+}
+
void TestApi::buildSingleFile()
{
qbs::SetupProjectParameters setupParams = defaultSetupParameters();
@@ -330,6 +349,8 @@ void TestApi::changeContent()
// Now check whether the data updates were done correctly.
projectData = project.projectData();
+ buildJob.reset(0);
+ project = qbs::Project();
job.reset(qbs::Project::setupProject(setupParams, m_logSink, 0));
waitForFinished(job.data());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
@@ -373,6 +394,9 @@ void TestApi::changeContent()
// Add a file to the top level of a product that does not have a "files" binding yet.
setupParams.setProjectFilePath(QDir::cleanPath(m_workingDataDir +
"/project-editing/project-with-no-files.qbs"));
+
+ buildJob.reset(0);
+ project = qbs::Project();
job.reset(qbs::Project::setupProject(setupParams, m_logSink, 0));
waitForFinished(job.data());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
@@ -390,6 +414,8 @@ void TestApi::changeContent()
waitForFinished(buildJob.data());
QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString()));
QVERIFY(rcvr.descriptions.contains("compiling main.cpp"));
+ buildJob.reset(0);
+ project = qbs::Project();
job.reset(qbs::Project::setupProject(setupParams, m_logSink, 0));
waitForFinished(job.data());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
@@ -532,6 +558,7 @@ void TestApi::installableFiles()
setupParams.setProjectFilePath(QDir::cleanPath(QLatin1String(SRCDIR "/../blackbox/testdata"
"/recursive_wildcards/recursive_wildcards.qbs")));
+ project = qbs::Project();
job.reset(qbs::Project::setupProject(setupParams, m_logSink, 0));
waitForFinished(job.data());
QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString()));
@@ -611,7 +638,7 @@ void TestApi::multiArch()
m_logSink, 0));
waitForFinished(setupJob.data());
QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString()));
- const qbs::Project &project = setupJob->project();
+ qbs::Project project = setupJob->project();
QCOMPARE(project.profile(), QLatin1String("qbs_autotests"));
const QList<qbs::ProductData> &products = project.projectData().products();
QCOMPARE(products.count(), 3);
@@ -653,6 +680,8 @@ void TestApi::multiArch()
// Error check: Try to build for the same profile twice.
overriddenValues.insert("project.targetProfile", hostProfile.name());
setupParams.setOverriddenValues(overriddenValues);
+ project = qbs::Project();
+ buildJob.reset(0);
setupJob.reset(qbs::Project::setupProject(setupParams, m_logSink, 0));
waitForFinished(setupJob.data());
QVERIFY(setupJob->error().hasError());
diff --git a/tests/auto/api/tst_api.h b/tests/auto/api/tst_api.h
index 7d1157512..5e75218ba 100644
--- a/tests/auto/api/tst_api.h
+++ b/tests/auto/api/tst_api.h
@@ -47,6 +47,7 @@ public:
private slots:
void initTestCase();
+ void buildGraphLocking();
void buildSingleFile();
void changeContent();
void disabledInstallGroup();