genericproject.cpp 14.9 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>
42
#include <projectexplorer/buildenvironmentwidget.h>
Tobias Hunger's avatar
Tobias Hunger committed
43
#include <projectexplorer/buildsteplist.h>
44
#include <projectexplorer/headerpath.h>
Tobias Hunger's avatar
Tobias Hunger committed
45 46
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
Roberto Raggi's avatar
Roberto Raggi committed
47
#include <projectexplorer/projectexplorerconstants.h>
48
#include <qtsupport/customexecutablerunconfiguration.h>
49
#include <utils/fileutils.h>
hjk's avatar
hjk committed
50
#include <utils/qtcassert.h>
Roberto Raggi's avatar
Roberto Raggi committed
51

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

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

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

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

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

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

77
    m_projectName      = fileInfo.completeBaseName();
78 79 80
    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();
81

82 83 84 85
    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);
86

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

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

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

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

102
    delete m_rootNode;
103 104
}

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

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

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

120
static QStringList readLines(const QString &absoluteFileName)
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
{
    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;
}

140
bool GenericProject::saveRawFileList(const QStringList &rawFileList)
141
{
142
    // Make sure we can open the file for writing
143 144 145 146 147 148 149
    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
150
    if (!saver.finalize(ICore::mainWindow()))
151 152 153
        return false;
    refresh(GenericProject::Files);
    return true;
154
}
155

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

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

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

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

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

177
    return saveRawFileList(newList);
178 179
}

180 181 182 183 184 185 186 187 188 189
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);
}

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
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);
}

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

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

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

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

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

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

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

237 238 239 240
    parseProject(options);

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

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

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

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

257
            foreach (const HeaderPath &headerPath, tc->systemHeaderPaths(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
QList<BuildConfigWidget*> GenericProject::subConfigWidgets()
Roberto Raggi's avatar
Roberto Raggi committed
402
{
403
    QList<BuildConfigWidget*> list;
404 405
    list << new BuildEnvironmentWidget;
    return list;
Roberto Raggi's avatar
Roberto Raggi committed
406 407
}

408
GenericProjectNode *GenericProject::rootProjectNode() const
Roberto Raggi's avatar
Roberto Raggi committed
409
{
410
    return m_rootNode;
Roberto Raggi's avatar
Roberto Raggi committed
411 412 413 414
}

QStringList GenericProject::files(FilesMode fileMode) const
{
415
    Q_UNUSED(fileMode)
416
    return m_files; // ### TODO: handle generated files here.
Roberto Raggi's avatar
Roberto Raggi committed
417 418
}

Tobias Hunger's avatar
Tobias Hunger committed
419
QStringList GenericProject::buildTargets() const
Roberto Raggi's avatar
Roberto Raggi committed
420 421 422 423 424 425 426
{
    QStringList targets;
    targets.append(QLatin1String("all"));
    targets.append(QLatin1String("clean"));
    return targets;
}

427 428 429 430
bool GenericProject::fromMap(const QVariantMap &map)
{
    if (!Project::fromMap(map))
        return false;
Roberto Raggi's avatar
Roberto Raggi committed
431

Tobias Hunger's avatar
Tobias Hunger committed
432 433 434
    Kit *defaultKit = KitManager::instance()->defaultKit();
    if (!activeTarget() && defaultKit)
        addTarget(createTarget(defaultKit));
Tobias Hunger's avatar
Tobias Hunger committed
435

436 437 438 439 440 441 442 443 444
    // 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())
445
            t->addRunConfiguration(new QtSupport::CustomExecutableRunConfiguration(t));
446 447
    }

Roberto Raggi's avatar
Roberto Raggi committed
448
    setIncludePaths(allIncludePaths());
449

Tobias Hunger's avatar
Tobias Hunger committed
450
    refresh(Everything);
451
    return true;
452 453
}

Roberto Raggi's avatar
Roberto Raggi committed
454
////////////////////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
455
//
Roberto Raggi's avatar
Roberto Raggi committed
456
// GenericProjectFile
hjk's avatar
hjk committed
457
//
Roberto Raggi's avatar
Roberto Raggi committed
458
////////////////////////////////////////////////////////////////////////////////////
459

460
GenericProjectFile::GenericProjectFile(GenericProject *parent, QString fileName, GenericProject::RefreshOptions options)
hjk's avatar
hjk committed
461
    : IDocument(parent),
462
      m_project(parent),
463 464
      m_fileName(fileName),
      m_options(options)
Roberto Raggi's avatar
Roberto Raggi committed
465 466
{ }

467
bool GenericProjectFile::save(QString *, const QString &, bool)
Roberto Raggi's avatar
Roberto Raggi committed
468 469 470 471 472 473
{
    return false;
}

QString GenericProjectFile::fileName() const
{
474
    return m_fileName;
Roberto Raggi's avatar
Roberto Raggi committed
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
}

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
502 503 504 505
void GenericProjectFile::rename(const QString &newName)
{
    // Can't happen
    Q_UNUSED(newName);
506
    QTC_CHECK(false);
dt's avatar
dt committed
507 508
}

hjk's avatar
hjk committed
509
IDocument::ReloadBehavior GenericProjectFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
Roberto Raggi's avatar
Roberto Raggi committed
510
{
511 512 513 514 515
    Q_UNUSED(state)
    Q_UNUSED(type)
    return BehaviorSilent;
}

516
bool GenericProjectFile::reload(QString *errorString, ReloadFlag flag, ChangeType type)
517
{
518
    Q_UNUSED(errorString)
519
    Q_UNUSED(flag)
520 521 522
    if (type == TypePermissions)
        return true;
    m_project->refresh(m_options);
523
    return true;
Roberto Raggi's avatar
Roberto Raggi committed
524
}
hjk's avatar
hjk committed
525 526 527

} // namespace Internal
} // namespace GenericProjectManager