genericproject.cpp 14.8 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Roberto Raggi's avatar
Roberto Raggi committed
2
**
hjk's avatar
hjk committed
3 4
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
Roberto Raggi's avatar
Roberto Raggi committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
Roberto Raggi's avatar
Roberto Raggi committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** 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.
Roberto Raggi's avatar
Roberto Raggi committed
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** 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
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
Roberto Raggi's avatar
Roberto Raggi committed
29 30

#include "genericproject.h"
Tobias Hunger's avatar
Tobias Hunger committed
31

dt's avatar
dt committed
32
#include "genericbuildconfiguration.h"
Tobias Hunger's avatar
Tobias Hunger committed
33
#include "genericmakestep.h"
hjk's avatar
hjk committed
34
#include "genericprojectconstants.h"
Roberto Raggi's avatar
Roberto Raggi committed
35

hjk's avatar
hjk committed
36 37 38 39 40
#include <coreplugin/documentmanager.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <cpptools/ModelManagerInterface.h>
#include <extensionsystem/pluginmanager.h>
41
#include <projectexplorer/abi.h>
Tobias Hunger's avatar
Tobias Hunger committed
42
#include <projectexplorer/buildsteplist.h>
43
#include <projectexplorer/headerpath.h>
Tobias Hunger's avatar
Tobias Hunger committed
44 45
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
Roberto Raggi's avatar
Roberto Raggi committed
46
#include <projectexplorer/projectexplorerconstants.h>
47
#include <qtsupport/customexecutablerunconfiguration.h>
48
#include <utils/fileutils.h>
hjk's avatar
hjk committed
49
#include <utils/qtcassert.h>
Roberto Raggi's avatar
Roberto Raggi committed
50

51 52
#include <QDir>
#include <QProcessEnvironment>
Roberto Raggi's avatar
Roberto Raggi committed
53

hjk's avatar
hjk committed
54
using namespace Core;
55
using namespace ProjectExplorer;
Roberto Raggi's avatar
Roberto Raggi committed
56

hjk's avatar
hjk committed
57 58 59
namespace GenericProjectManager {
namespace Internal {

60
////////////////////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
61
//
62
// GenericProject
hjk's avatar
hjk committed
63
//
64 65
////////////////////////////////////////////////////////////////////////////////////

Roberto Raggi's avatar
Roberto Raggi committed
66
GenericProject::GenericProject(Manager *manager, const QString &fileName)
67
    : m_manager(manager),
Tobias Hunger's avatar
Tobias Hunger committed
68
      m_fileName(fileName)
Roberto Raggi's avatar
Roberto Raggi committed
69
{
hjk's avatar
hjk committed
70 71
    setProjectContext(Context(GenericProjectManager::Constants::PROJECTCONTEXT));
    setProjectLanguage(Context(ProjectExplorer::Constants::LANG_CXX));
72

73
    QFileInfo fileInfo(m_fileName);
74 75
    QDir dir = fileInfo.dir();

76
    m_projectName      = fileInfo.completeBaseName();
77 78 79
    m_filesFileName    = QFileInfo(dir, m_projectName + QLatin1String(".files")).absoluteFilePath();
    m_includesFileName = QFileInfo(dir, m_projectName + QLatin1String(".includes")).absoluteFilePath();
    m_configFileName   = QFileInfo(dir, m_projectName + QLatin1String(".config")).absoluteFilePath();
80

81 82 83 84
    m_creatorIDocument  = new GenericProjectFile(this, m_fileName, GenericProject::Everything);
    m_filesIDocument    = new GenericProjectFile(this, m_filesFileName, GenericProject::Files);
    m_includesIDocument = new GenericProjectFile(this, m_includesFileName, GenericProject::Configuration);
    m_configIDocument   = new GenericProjectFile(this, m_configFileName, GenericProject::Configuration);
85

hjk's avatar
hjk committed
86 87 88 89
    DocumentManager::addDocument(m_creatorIDocument);
    DocumentManager::addDocument(m_filesIDocument);
    DocumentManager::addDocument(m_includesIDocument);
    DocumentManager::addDocument(m_configIDocument);
90

91
    m_rootNode = new GenericProjectNode(this, m_creatorIDocument);
92

93
    m_manager->registerProject(this);
Roberto Raggi's avatar
Roberto Raggi committed
94 95 96 97
}

GenericProject::~GenericProject()
{
98
    m_codeModelFuture.cancel();
99
    m_manager->unregisterProject(this);
100

101
    delete m_rootNode;
102 103
}

104
QString GenericProject::filesFileName() const
hjk's avatar
hjk committed
105 106 107
{
    return m_filesFileName;
}
108 109

QString GenericProject::includesFileName() const
hjk's avatar
hjk committed
110 111 112
{
    return m_includesFileName;
}
113 114

QString GenericProject::configFileName() const
hjk's avatar
hjk committed
115 116 117
{
    return m_configFileName;
}
118

119
static QStringList readLines(const QString &absoluteFileName)
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
{
    QStringList lines;

    QFile file(absoluteFileName);
    if (file.open(QFile::ReadOnly)) {
        QTextStream stream(&file);

        forever {
            QString line = stream.readLine();
            if (line.isNull())
                break;

            lines.append(line);
        }
    }

    return lines;
}

139
bool GenericProject::saveRawFileList(const QStringList &rawFileList)
140
{
141
    // Make sure we can open the file for writing
142 143 144 145 146 147 148
    Utils::FileSaver saver(filesFileName(), QIODevice::Text);
    if (!saver.hasError()) {
        QTextStream stream(saver.file());
        foreach (const QString &filePath, rawFileList)
            stream << filePath << QLatin1Char('\n');
        saver.setResult(&stream);
    }
hjk's avatar
hjk committed
149
    if (!saver.finalize(ICore::mainWindow()))
150 151 152
        return false;
    refresh(GenericProject::Files);
    return true;
153
}
154

155 156
bool GenericProject::addFiles(const QStringList &filePaths)
{
157 158 159 160 161
    QStringList newList = m_rawFileList;

    QDir baseDir(QFileInfo(m_fileName).dir());
    foreach (const QString &filePath, filePaths)
        newList.append(baseDir.relativeFilePath(filePath));
162

163
    return saveRawFileList(newList);
164 165 166 167
}

bool GenericProject::removeFiles(const QStringList &filePaths)
{
168
    QStringList newList = m_rawFileList;
169

170 171 172 173
    foreach (const QString &filePath, filePaths) {
        QHash<QString, QString>::iterator i = m_rawListEntries.find(filePath);
        if (i != m_rawListEntries.end())
            newList.removeOne(i.value());
174
    }
175

176
    return saveRawFileList(newList);
177 178
}

179 180 181 182 183 184 185 186 187 188
bool GenericProject::setFiles(const QStringList &filePaths)
{
    QStringList newList;
    QDir baseDir(QFileInfo(m_fileName).dir());
    foreach (const QString &filePath, filePaths)
        newList.append(baseDir.relativeFilePath(filePath));

    return saveRawFileList(newList);
}

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
bool GenericProject::renameFile(const QString &filePath, const QString &newFilePath)
{
    QStringList newList = m_rawFileList;

    QHash<QString, QString>::iterator i = m_rawListEntries.find(filePath);
    if (i != m_rawListEntries.end()) {
        int index = newList.indexOf(i.value());
        if (index != -1) {
            QDir baseDir(QFileInfo(m_fileName).dir());
            newList.replace(index, baseDir.relativeFilePath(newFilePath));
        }
    }

    return saveRawFileList(newList);
}

205
void GenericProject::parseProject(RefreshOptions options)
Roberto Raggi's avatar
Roberto Raggi committed
206
{
207 208 209
    if (options & Files) {
        m_rawListEntries.clear();
        m_rawFileList = readLines(filesFileName());
210
        m_files = processEntries(m_rawFileList, &m_rawListEntries);
211
    }
212

213
    if (options & Configuration) {
214
        m_projectIncludePaths = processEntries(readLines(includesFileName()));
215

216 217
        // TODO: Possibly load some configuration from the project file
        //QSettings projectInfo(m_fileName, QSettings::IniFormat);
Roberto Raggi's avatar
Roberto Raggi committed
218

219
        m_defines.clear();
Roberto Raggi's avatar
Roberto Raggi committed
220

221 222 223 224
        QFile configFile(configFileName());
        if (configFile.open(QFile::ReadOnly))
            m_defines = configFile.readAll();
    }
225

226 227
    if (options & Files)
        emit fileListChanged();
Roberto Raggi's avatar
Roberto Raggi committed
228 229
}

230
void GenericProject::refresh(RefreshOptions options)
Roberto Raggi's avatar
Roberto Raggi committed
231
{
232 233 234
    QSet<QString> oldFileList;
    if (!(options & Configuration))
        oldFileList = m_files.toSet();
235

236 237 238
    parseProject(options);

    if (options & Files)
239
        m_rootNode->refresh(oldFileList);
Roberto Raggi's avatar
Roberto Raggi committed
240

241
    CPlusPlus::CppModelManagerInterface *modelManager =
242
        CPlusPlus::CppModelManagerInterface::instance();
Roberto Raggi's avatar
Roberto Raggi committed
243

244
    if (modelManager) {
245
        CPlusPlus::CppModelManagerInterface::ProjectInfo pinfo = modelManager->projectInfo(this);
246 247 248
        pinfo.clearProjectParts();
        CPlusPlus::CppModelManagerInterface::ProjectPart::Ptr part(
                    new CPlusPlus::CppModelManagerInterface::ProjectPart);
Roberto Raggi's avatar
Roberto Raggi committed
249

250 251
        Kit *k = activeTarget() ? activeTarget()->kit() : KitManager::instance()->defaultKit();
        ToolChain *tc = k ? ToolChainKitInformation::toolChain(k) : 0;
Tobias Hunger's avatar
Tobias Hunger committed
252
        if (tc) {
253 254
            QStringList cxxflags; // FIXME: Can we do better?
            part->defines = tc->predefinedMacros(cxxflags);
255
            part->defines += '\n';
Roberto Raggi's avatar
Roberto Raggi committed
256

257
            foreach (const HeaderPath &headerPath, tc->systemHeaderPaths(cxxflags, SysRootKitInformation::sysRoot(k))) {
258
                if (headerPath.kind() == HeaderPath::FrameworkHeaderPath)
259
                    part->frameworkPaths.append(headerPath.path());
260
                else
261
                    part->includePaths.append(headerPath.path());
262
            }
Roberto Raggi's avatar
Roberto Raggi committed
263 264
        }

265 266
        part->includePaths += allIncludePaths();
        part->defines += m_defines;
Roberto Raggi's avatar
Roberto Raggi committed
267 268

        // ### add _defines.
269 270
        part->sourceFiles = files();
        part->sourceFiles += generated();
Roberto Raggi's avatar
Roberto Raggi committed
271

272 273 274
        QStringList filesToUpdate;

        if (options & Configuration) {
275
            filesToUpdate = part->sourceFiles;
276
            filesToUpdate.append(QLatin1String("<configuration>")); // XXX don't hardcode configuration file name
277 278
            // Full update, if there's a code model update, cancel it
            m_codeModelFuture.cancel();
279 280 281 282 283 284
        } else if (options & Files) {
            // Only update files that got added to the list
            QSet<QString> newFileList = m_files.toSet();
            newFileList.subtract(oldFileList);
            filesToUpdate.append(newFileList.toList());
        }
285

286 287
        pinfo.appendProjectPart(part);

Roberto Raggi's avatar
Roberto Raggi committed
288
        modelManager->updateProjectInfo(pinfo);
289
        m_codeModelFuture = modelManager->updateSourceFiles(filesToUpdate);
Roberto Raggi's avatar
Roberto Raggi committed
290 291 292
    }
}

293
/**
294 295 296 297 298
 * Expands environment variables in the given \a string when they are written
 * like $$(VARIABLE).
 */
static void expandEnvironmentVariables(const QProcessEnvironment &env, QString &string)
{
299
    static QRegExp candidate(QLatin1String("\\$\\$\\((.+)\\)"));
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315

    int index = candidate.indexIn(string);
    while (index != -1) {
        const QString value = env.value(candidate.cap(1));

        string.replace(index, candidate.matchedLength(), value);
        index += value.length();

        index = candidate.indexIn(string, index);
    }
}

/**
 * Expands environment variables and converts the path from relative to the
 * project to an absolute path.
 *
316
 * The \a map variable is an optional argument that will map the returned
317
 * absolute paths back to their original \a entries.
318
 */
319 320
QStringList GenericProject::processEntries(const QStringList &paths,
                                           QHash<QString, QString> *map) const
Roberto Raggi's avatar
Roberto Raggi committed
321
{
322
    const QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
323
    const QDir projectDir(QFileInfo(m_fileName).dir());
324

Roberto Raggi's avatar
Roberto Raggi committed
325
    QStringList absolutePaths;
326
    foreach (const QString &path, paths) {
327 328
        QString trimmedPath = path.trimmed();
        if (trimmedPath.isEmpty())
329 330
            continue;

331 332 333
        expandEnvironmentVariables(env, trimmedPath);

        const QString absPath = QFileInfo(projectDir, trimmedPath).absoluteFilePath();
334 335
        absolutePaths.append(absPath);
        if (map)
336
            map->insert(absPath, trimmedPath);
Roberto Raggi's avatar
Roberto Raggi committed
337 338 339 340 341
    }
    absolutePaths.removeDuplicates();
    return absolutePaths;
}

Roberto Raggi's avatar
Roberto Raggi committed
342 343 344
QStringList GenericProject::allIncludePaths() const
{
    QStringList paths;
345 346
    paths += m_includePaths;
    paths += m_projectIncludePaths;
Roberto Raggi's avatar
Roberto Raggi committed
347 348 349 350 351
    paths.removeDuplicates();
    return paths;
}

QStringList GenericProject::projectIncludePaths() const
hjk's avatar
hjk committed
352 353 354
{
    return m_projectIncludePaths;
}
Roberto Raggi's avatar
Roberto Raggi committed
355

Roberto Raggi's avatar
Roberto Raggi committed
356
QStringList GenericProject::files() const
hjk's avatar
hjk committed
357 358 359
{
    return m_files;
}
Roberto Raggi's avatar
Roberto Raggi committed
360 361

QStringList GenericProject::generated() const
hjk's avatar
hjk committed
362 363 364
{
    return m_generated;
}
Roberto Raggi's avatar
Roberto Raggi committed
365 366

QStringList GenericProject::includePaths() const
hjk's avatar
hjk committed
367 368 369
{
    return m_includePaths;
}
Roberto Raggi's avatar
Roberto Raggi committed
370 371

void GenericProject::setIncludePaths(const QStringList &includePaths)
hjk's avatar
hjk committed
372 373 374
{
    m_includePaths = includePaths;
}
Roberto Raggi's avatar
Roberto Raggi committed
375

Roberto Raggi's avatar
Roberto Raggi committed
376
QByteArray GenericProject::defines() const
hjk's avatar
hjk committed
377 378 379
{
    return m_defines;
}
Roberto Raggi's avatar
Roberto Raggi committed
380

381
QString GenericProject::displayName() const
Roberto Raggi's avatar
Roberto Raggi committed
382
{
383
    return m_projectName;
Roberto Raggi's avatar
Roberto Raggi committed
384 385
}

hjk's avatar
hjk committed
386
Id GenericProject::id() const
Tobias Hunger's avatar
Tobias Hunger committed
387
{
hjk's avatar
hjk committed
388
    return Id(Constants::GENERICPROJECT_ID);
Tobias Hunger's avatar
Tobias Hunger committed
389 390
}

hjk's avatar
hjk committed
391
IDocument *GenericProject::document() const
Roberto Raggi's avatar
Roberto Raggi committed
392
{
393
    return m_creatorIDocument;
Roberto Raggi's avatar
Roberto Raggi committed
394 395
}

396
IProjectManager *GenericProject::projectManager() const
Roberto Raggi's avatar
Roberto Raggi committed
397
{
398
    return m_manager;
Roberto Raggi's avatar
Roberto Raggi committed
399 400
}

401
GenericProjectNode *GenericProject::rootProjectNode() const
Roberto Raggi's avatar
Roberto Raggi committed
402
{
403
    return m_rootNode;
Roberto Raggi's avatar
Roberto Raggi committed
404 405 406 407
}

QStringList GenericProject::files(FilesMode fileMode) const
{
408
    Q_UNUSED(fileMode)
409
    return m_files; // ### TODO: handle generated files here.
Roberto Raggi's avatar
Roberto Raggi committed
410 411
}

Tobias Hunger's avatar
Tobias Hunger committed
412
QStringList GenericProject::buildTargets() const
Roberto Raggi's avatar
Roberto Raggi committed
413 414 415 416 417 418 419
{
    QStringList targets;
    targets.append(QLatin1String("all"));
    targets.append(QLatin1String("clean"));
    return targets;
}

420 421 422 423
bool GenericProject::fromMap(const QVariantMap &map)
{
    if (!Project::fromMap(map))
        return false;
Roberto Raggi's avatar
Roberto Raggi committed
424

Tobias Hunger's avatar
Tobias Hunger committed
425 426 427
    Kit *defaultKit = KitManager::instance()->defaultKit();
    if (!activeTarget() && defaultKit)
        addTarget(createTarget(defaultKit));
Tobias Hunger's avatar
Tobias Hunger committed
428

429 430 431 432 433 434 435 436 437
    // Sanity check: We need both a buildconfiguration and a runconfiguration!
    QList<Target *> targetList = targets();
    foreach (Target *t, targetList) {
        if (!t->activeBuildConfiguration()) {
            removeTarget(t);
            delete t;
            continue;
        }
        if (!t->activeRunConfiguration())
438
            t->addRunConfiguration(new QtSupport::CustomExecutableRunConfiguration(t));
439 440
    }

Roberto Raggi's avatar
Roberto Raggi committed
441
    setIncludePaths(allIncludePaths());
442

Tobias Hunger's avatar
Tobias Hunger committed
443
    refresh(Everything);
444
    return true;
445 446
}

Roberto Raggi's avatar
Roberto Raggi committed
447
////////////////////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
448
//
Roberto Raggi's avatar
Roberto Raggi committed
449
// GenericProjectFile
hjk's avatar
hjk committed
450
//
Roberto Raggi's avatar
Roberto Raggi committed
451
////////////////////////////////////////////////////////////////////////////////////
452

453
GenericProjectFile::GenericProjectFile(GenericProject *parent, QString fileName, GenericProject::RefreshOptions options)
hjk's avatar
hjk committed
454
    : IDocument(parent),
455
      m_project(parent),
456 457
      m_fileName(fileName),
      m_options(options)
Roberto Raggi's avatar
Roberto Raggi committed
458 459
{ }

460
bool GenericProjectFile::save(QString *, const QString &, bool)
Roberto Raggi's avatar
Roberto Raggi committed
461 462 463 464 465 466
{
    return false;
}

QString GenericProjectFile::fileName() const
{
467
    return m_fileName;
Roberto Raggi's avatar
Roberto Raggi committed
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
}

QString GenericProjectFile::defaultPath() const
{
    return QString();
}

QString GenericProjectFile::suggestedFileName() const
{
    return QString();
}

QString GenericProjectFile::mimeType() const
{
    return Constants::GENERICMIMETYPE;
}

bool GenericProjectFile::isModified() const
{
    return false;
}

bool GenericProjectFile::isSaveAsAllowed() const
{
    return false;
}

dt's avatar
dt committed
495 496 497 498
void GenericProjectFile::rename(const QString &newName)
{
    // Can't happen
    Q_UNUSED(newName);
499
    QTC_CHECK(false);
dt's avatar
dt committed
500 501
}

hjk's avatar
hjk committed
502
IDocument::ReloadBehavior GenericProjectFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
Roberto Raggi's avatar
Roberto Raggi committed
503
{
504 505 506 507 508
    Q_UNUSED(state)
    Q_UNUSED(type)
    return BehaviorSilent;
}

509
bool GenericProjectFile::reload(QString *errorString, ReloadFlag flag, ChangeType type)
510
{
511
    Q_UNUSED(errorString)
512
    Q_UNUSED(flag)
513 514 515
    if (type == TypePermissions)
        return true;
    m_project->refresh(m_options);
516
    return true;
Roberto Raggi's avatar
Roberto Raggi committed
517
}
hjk's avatar
hjk committed
518 519 520

} // namespace Internal
} // namespace GenericProjectManager