From 5ffdee71e50e9593794e66139baa0c355e0bd88e Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Fri, 11 Jul 2014 17:19:04 +0200 Subject: 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 --- src/lib/corelib/api/internaljobs.cpp | 10 +++ src/lib/corelib/corelib.qbs | 2 + src/lib/corelib/language/language.cpp | 5 +- src/lib/corelib/language/language.h | 4 +- src/lib/corelib/tools/buildgraphlocker.cpp | 96 ++++++++++++++++++++++ src/lib/corelib/tools/buildgraphlocker.h | 63 ++++++++++++++ src/lib/corelib/tools/tools.pri | 2 + .../api/testdata/buildgraph-locking/project.qbs | 4 + tests/auto/api/tst_api.cpp | 31 ++++++- tests/auto/api/tst_api.h | 1 + 10 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 src/lib/corelib/tools/buildgraphlocker.cpp create mode 100644 src/lib/corelib/tools/buildgraphlocker.h create mode 100644 tests/auto/api/testdata/buildgraph-locking/project.qbs 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 #include #include +#include #include #include #include @@ -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 #include #include +#include #include #include #include @@ -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 fileExistsResults; // Results of calls to "File.exists()". QHash fileLastModifiedResults; // Results of calls to "File.lastModified()". QScopedPointer 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 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 + +#include +#include +#include + +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 + +#define HAS_QLOCKFILE (QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)) + +#if HAS_QLOCKFILE +#include +#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 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 &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(); -- cgit v1.2.3