genericproject.cpp 15 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
        expandEnvironmentVariables(env, trimmedPath);

333 334
        trimmedPath = Utils::FileName::fromUserInput(trimmedPath).toString();

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

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

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

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

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

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

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

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

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

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

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

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

403
QList<BuildConfigWidget*> GenericProject::subConfigWidgets()
Roberto Raggi's avatar
Roberto Raggi committed
404
{
405
    QList<BuildConfigWidget*> list;
406 407
    list << new BuildEnvironmentWidget;
    return list;
Roberto Raggi's avatar
Roberto Raggi committed
408 409
}

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

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

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

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

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

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

Roberto Raggi's avatar
Roberto Raggi committed
450
    setIncludePaths(allIncludePaths());
451

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

Roberto Raggi's avatar
Roberto Raggi committed
456
////////////////////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
457
//
Roberto Raggi's avatar
Roberto Raggi committed
458
// GenericProjectFile
hjk's avatar
hjk committed
459
//
Roberto Raggi's avatar
Roberto Raggi committed
460
////////////////////////////////////////////////////////////////////////////////////
461

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

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

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

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

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

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

} // namespace Internal
} // namespace GenericProjectManager