genericproject.cpp 14.7 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 253
        if (tc) {
            part->defines = tc->predefinedMacros(QStringList());
254
            part->defines += '\n';
Roberto Raggi's avatar
Roberto Raggi committed
255

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

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

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

271 272 273
        QStringList filesToUpdate;

        if (options & Configuration) {
274
            filesToUpdate = part->sourceFiles;
275
            filesToUpdate.append(QLatin1String("<configuration>")); // XXX don't hardcode configuration file name
276 277
            // Full update, if there's a code model update, cancel it
            m_codeModelFuture.cancel();
278 279 280 281 282 283
        } 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());
        }
284

285 286
        pinfo.appendProjectPart(part);

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

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

    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.
 *
315
 * The \a map variable is an optional argument that will map the returned
316
 * absolute paths back to their original \a entries.
317
 */
318 319
QStringList GenericProject::processEntries(const QStringList &paths,
                                           QHash<QString, QString> *map) const
Roberto Raggi's avatar
Roberto Raggi committed
320
{
321
    const QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
322
    const QDir projectDir(QFileInfo(m_fileName).dir());
323

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

330 331 332
        expandEnvironmentVariables(env, trimmedPath);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

428 429 430 431 432 433 434 435 436
    // 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())
437
            t->addRunConfiguration(new QtSupport::CustomExecutableRunConfiguration(t));
438 439
    }

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

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

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

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

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

QString GenericProjectFile::fileName() const
{
466
    return m_fileName;
Roberto Raggi's avatar
Roberto Raggi committed
467 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
}

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
494 495 496 497
void GenericProjectFile::rename(const QString &newName)
{
    // Can't happen
    Q_UNUSED(newName);
498
    QTC_CHECK(false);
dt's avatar
dt committed
499 500
}

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

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

} // namespace Internal
} // namespace GenericProjectManager