qt5base-lts/qmake/library/qmakevfs.cpp
Oswald Buddenhagen f12fd482f5 qmake: fix file id mapping lifetime management
turns out that flushing the ids together with the ProFile cache was an
abysmal idea, as the latter expires after a few seconds after loading
the project, while references to the ProFiles (and thus the underlying
file ids) are kept for as long as the project stays loaded. the early
flush would cause re-use of the file ids, which would wreak all kinds of
havoc when re-loading projects.

but just ref-counting the vfs class is insufficient as well, as then the
ProFile cache (which expires after a timeout) could outlive all VFS
instances and thus refer to ids which were discarded and later re-used.

so we ref-count, and additionally let the cache instance hold a
reference to the vfs class.

this is sync-up with qt creator; no actual effect on qmake itself.

amends 190aa94be.

Change-Id: Idd4478ffc1c2405b3b83df32fba45b1f687f6a18
Reviewed-by: Robert Loehning <robert.loehning@qt.io>
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
(cherry picked from qtcreator/d03fb350672d311dccc06f0bcb4da744a3c99745)
(cherry picked from qtcreator/1ddfb443b686ef04cc0e28363308ce70d01f0d73)
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@qt.io>
2018-08-24 12:16:37 +00:00

301 lines
7.9 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the qmake application of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qmakevfs.h"
#include "ioutils.h"
using namespace QMakeInternal;
#include <qdir.h>
#include <qfile.h>
#include <qfileinfo.h>
#ifndef QT_NO_TEXTCODEC
#include <qtextcodec.h>
#endif
#define fL1S(s) QString::fromLatin1(s)
QT_BEGIN_NAMESPACE
QMakeVfs::QMakeVfs()
#ifndef PROEVALUATOR_FULL
: m_magicMissing(fL1S("missing"))
, m_magicExisting(fL1S("existing"))
#endif
{
#ifndef QT_NO_TEXTCODEC
m_textCodec = 0;
#endif
ref();
}
QMakeVfs::~QMakeVfs()
{
deref();
}
void QMakeVfs::ref()
{
#ifdef PROEVALUATOR_THREAD_SAFE
QMutexLocker locker(&s_mutex);
#endif
++s_refCount;
}
void QMakeVfs::deref()
{
#ifdef PROEVALUATOR_THREAD_SAFE
QMutexLocker locker(&s_mutex);
#endif
if (!--s_refCount) {
s_fileIdCounter = 0;
s_fileIdMap.clear();
s_idFileMap.clear();
}
}
#ifdef PROPARSER_THREAD_SAFE
QMutex QMakeVfs::s_mutex;
#endif
int QMakeVfs::s_refCount;
QAtomicInt QMakeVfs::s_fileIdCounter;
QHash<QString, int> QMakeVfs::s_fileIdMap;
QHash<int, QString> QMakeVfs::s_idFileMap;
int QMakeVfs::idForFileName(const QString &fn, VfsFlags flags)
{
#ifdef PROEVALUATOR_DUAL_VFS
{
# ifdef PROPARSER_THREAD_SAFE
QMutexLocker locker(&m_vmutex);
# endif
int idx = (flags & VfsCumulative) ? 1 : 0;
if (flags & VfsCreate) {
int &id = m_virtualFileIdMap[idx][fn];
if (!id) {
id = ++s_fileIdCounter;
m_virtualIdFileMap[id] = fn;
}
return id;
}
int id = m_virtualFileIdMap[idx].value(fn);
if (id || (flags & VfsCreatedOnly))
return id;
}
#endif
if (!(flags & VfsAccessedOnly)) {
#ifdef PROPARSER_THREAD_SAFE
QMutexLocker locker(&s_mutex);
#endif
int &id = s_fileIdMap[fn];
if (!id) {
id = ++s_fileIdCounter;
s_idFileMap[id] = fn;
}
return id;
}
return s_fileIdMap.value(fn);
}
QString QMakeVfs::fileNameForId(int id)
{
#ifdef PROEVALUATOR_DUAL_VFS
{
# ifdef PROPARSER_THREAD_SAFE
QMutexLocker locker(&m_vmutex);
# endif
const QString &fn = m_virtualIdFileMap.value(id);
if (!fn.isEmpty())
return fn;
}
#endif
#ifdef PROPARSER_THREAD_SAFE
QMutexLocker locker(&s_mutex);
#endif
return s_idFileMap.value(id);
}
bool QMakeVfs::writeFile(int id, QIODevice::OpenMode mode, VfsFlags flags,
const QString &contents, QString *errStr)
{
#ifndef PROEVALUATOR_FULL
# ifdef PROEVALUATOR_THREAD_SAFE
QMutexLocker locker(&m_mutex);
# endif
QString *cont = &m_files[id];
Q_UNUSED(flags)
if (mode & QIODevice::Append)
*cont += contents;
else
*cont = contents;
Q_UNUSED(errStr)
return true;
#else
QFileInfo qfi(fileNameForId(id));
if (!QDir::current().mkpath(qfi.path())) {
*errStr = fL1S("Cannot create parent directory");
return false;
}
QByteArray bytes = contents.toLocal8Bit();
QFile cfile(qfi.filePath());
if (!(mode & QIODevice::Append) && cfile.open(QIODevice::ReadOnly | QIODevice::Text)) {
if (cfile.readAll() == bytes) {
if (flags & VfsExecutable) {
cfile.setPermissions(cfile.permissions()
| QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther);
} else {
cfile.setPermissions(cfile.permissions()
& ~(QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther));
}
return true;
}
cfile.close();
}
if (!cfile.open(mode | QIODevice::WriteOnly | QIODevice::Text)) {
*errStr = cfile.errorString();
return false;
}
cfile.write(bytes);
cfile.close();
if (cfile.error() != QFile::NoError) {
*errStr = cfile.errorString();
return false;
}
if (flags & VfsExecutable)
cfile.setPermissions(cfile.permissions()
| QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther);
return true;
#endif
}
QMakeVfs::ReadResult QMakeVfs::readFile(int id, QString *contents, QString *errStr)
{
#ifndef PROEVALUATOR_FULL
# ifdef PROEVALUATOR_THREAD_SAFE
QMutexLocker locker(&m_mutex);
# endif
auto it = m_files.constFind(id);
if (it != m_files.constEnd()) {
if (it->constData() == m_magicMissing.constData()) {
*errStr = fL1S("No such file or directory");
return ReadNotFound;
}
if (it->constData() != m_magicExisting.constData()) {
*contents = *it;
return ReadOk;
}
}
#endif
QFile file(fileNameForId(id));
if (!file.open(QIODevice::ReadOnly)) {
if (!file.exists()) {
#ifndef PROEVALUATOR_FULL
m_files[id] = m_magicMissing;
#endif
*errStr = fL1S("No such file or directory");
return ReadNotFound;
}
*errStr = file.errorString();
return ReadOtherError;
}
#ifndef PROEVALUATOR_FULL
m_files[id] = m_magicExisting;
#endif
QByteArray bcont = file.readAll();
if (bcont.startsWith("\xef\xbb\xbf")) {
// UTF-8 BOM will cause subtle errors
*errStr = fL1S("Unexpected UTF-8 BOM");
return ReadOtherError;
}
*contents =
#ifndef QT_NO_TEXTCODEC
m_textCodec ? m_textCodec->toUnicode(bcont) :
#endif
QString::fromLocal8Bit(bcont);
return ReadOk;
}
bool QMakeVfs::exists(const QString &fn, VfsFlags flags)
{
#ifndef PROEVALUATOR_FULL
# ifdef PROEVALUATOR_THREAD_SAFE
QMutexLocker locker(&m_mutex);
# endif
int id = idForFileName(fn, flags);
auto it = m_files.constFind(id);
if (it != m_files.constEnd())
return it->constData() != m_magicMissing.constData();
#else
Q_UNUSED(flags)
#endif
bool ex = IoUtils::fileType(fn) == IoUtils::FileIsRegular;
#ifndef PROEVALUATOR_FULL
m_files[id] = ex ? m_magicExisting : m_magicMissing;
#endif
return ex;
}
#ifndef PROEVALUATOR_FULL
// This should be called when the sources may have changed (e.g., VCS update).
void QMakeVfs::invalidateCache()
{
# ifdef PROEVALUATOR_THREAD_SAFE
QMutexLocker locker(&m_mutex);
# endif
auto it = m_files.begin(), eit = m_files.end();
while (it != eit) {
if (it->constData() == m_magicMissing.constData()
||it->constData() == m_magicExisting.constData())
it = m_files.erase(it);
else
++it;
}
}
// This should be called when generated files may have changed (e.g., actual build).
void QMakeVfs::invalidateContents()
{
# ifdef PROEVALUATOR_THREAD_SAFE
QMutexLocker locker(&m_mutex);
# endif
m_files.clear();
}
#endif
#ifndef QT_NO_TEXTCODEC
void QMakeVfs::setTextCodec(const QTextCodec *textCodec)
{
m_textCodec = textCodec;
}
#endif
QT_END_NAMESPACE