autotoolsproject.cpp 14.7 KB
Newer Older
1 2
/**************************************************************************
**
3
** Copyright (C) 2014 Openismus GmbH.
hjk's avatar
hjk committed
4 5 6
** Authors: Peter Penz (ppenz@openismus.com)
**          Patricia Santana Cruz (patriciasantanacruz@gmail.com)
** Contact: http://www.qt-project.org/legal
7
**
hjk's avatar
hjk committed
8
** This file is part of Qt Creator.
9
**
hjk's avatar
hjk committed
10 11 12 13 14 15 16
** 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.
17 18
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
19 20 21 22 23 24 25 26 27
** 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
28 29
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
30
****************************************************************************/
31 32

#include "autotoolsproject.h"
Tobias Hunger's avatar
Tobias Hunger committed
33
#include "autotoolsbuildconfiguration.h"
34 35 36 37 38 39 40 41 42
#include "autotoolsprojectconstants.h"
#include "autotoolsmanager.h"
#include "autotoolsprojectnode.h"
#include "autotoolsprojectfile.h"
#include "autotoolsopenprojectwizard.h"
#include "makestep.h"
#include "makefileparserthread.h"

#include <projectexplorer/abi.h>
43
#include <projectexplorer/toolchain.h>
Tobias Hunger's avatar
Tobias Hunger committed
44 45
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/kitinformation.h>
46 47 48
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/projectexplorerconstants.h>
Tobias Hunger's avatar
Tobias Hunger committed
49
#include <projectexplorer/target.h>
50
#include <projectexplorer/headerpath.h>
51
#include <extensionsystem/pluginmanager.h>
52
#include <cpptools/cppmodelmanager.h>
53
#include <coreplugin/icore.h>
54
#include <coreplugin/icontext.h>
55
#include <utils/qtcassert.h>
56
#include <utils/filesystemwatcher.h>
57

58 59 60 61 62 63 64 65
#include <QFileInfo>
#include <QTimer>
#include <QPointer>
#include <QApplication>
#include <QCursor>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
66 67 68 69 70 71 72 73 74 75 76 77 78

using namespace AutotoolsProjectManager;
using namespace AutotoolsProjectManager::Internal;
using namespace ProjectExplorer;

AutotoolsProject::AutotoolsProject(AutotoolsManager *manager, const QString &fileName) :
    m_manager(manager),
    m_fileName(fileName),
    m_files(),
    m_file(new AutotoolsProjectFile(this, m_fileName)),
    m_rootNode(new AutotoolsProjectNode(this, m_file)),
    m_fileWatcher(new Utils::FileSystemWatcher(this)),
    m_watchedFiles(),
Tobias Hunger's avatar
Tobias Hunger committed
79
    m_makefileParserThread(0)
80
{
81
    setId(Constants::AUTOTOOLS_PROJECT_ID);
82
    setProjectContext(Core::Context(Constants::PROJECT_CONTEXT));
83
    setProjectLanguages(Core::Context(ProjectExplorer::Constants::LANG_CXX));
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111

    const QFileInfo fileInfo(m_fileName);
    m_projectName = fileInfo.absoluteDir().dirName();
    m_rootNode->setDisplayName(fileInfo.absoluteDir().dirName());
}

AutotoolsProject::~AutotoolsProject()
{
    // Although ProjectExplorer::ProjectNode is a QObject, the ctor
    // does not allow to specify the parent. Manually setting the
    // parent would be possible, but we use the same approach as in the
    // other project managers and delete the node manually (TBD).
    //
    delete m_rootNode;
    m_rootNode = 0;

    if (m_makefileParserThread != 0) {
        m_makefileParserThread->wait();
        delete m_makefileParserThread;
        m_makefileParserThread = 0;
    }
}

QString AutotoolsProject::displayName() const
{
    return m_projectName;
}

112
Core::IDocument *AutotoolsProject::document() const
113 114 115 116
{
    return m_file;
}

117
IProjectManager *AutotoolsProject::projectManager() const
118 119 120 121
{
    return m_manager;
}

122 123 124
QString AutotoolsProject::defaultBuildDirectory(const QString &projectPath)
{
    return QFileInfo(projectPath).absolutePath();
125 126
}

127
ProjectNode *AutotoolsProject::rootProjectNode() const
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
{
    return m_rootNode;
}

QStringList AutotoolsProject::files(FilesMode fileMode) const
{
    Q_UNUSED(fileMode);
    return m_files;
}

// This function, is called at the very beginning, to
// restore the settings if there are some stored.
bool AutotoolsProject::fromMap(const QVariantMap &map)
{
    if (!Project::fromMap(map))
        return false;

    connect(m_fileWatcher, SIGNAL(fileChanged(QString)),
            this, SLOT(onFileChanged(QString)));

    // Load the project tree structure.
149
    loadProjectTree();
Tobias Hunger's avatar
Tobias Hunger committed
150

151
    Kit *defaultKit = KitManager::defaultKit();
Tobias Hunger's avatar
Tobias Hunger committed
152 153
    if (!activeTarget() && defaultKit)
        addTarget(createTarget(defaultKit));
154 155 156 157

    return true;
}

158
void AutotoolsProject::loadProjectTree()
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
{
    if (m_makefileParserThread != 0) {
        // The thread is still busy parsing a previus configuration.
        // Wait until the thread has been finished and delete it.
        // TODO: Discuss whether blocking is acceptable.
        disconnect(m_makefileParserThread, SIGNAL(finished()),
                   this, SLOT(makefileParsingFinished()));
        m_makefileParserThread->wait();
        delete m_makefileParserThread;
        m_makefileParserThread = 0;
    }

    // Parse the makefile asynchronously in a thread
    m_makefileParserThread = new MakefileParserThread(m_fileName);

    connect(m_makefileParserThread, SIGNAL(started()),
            this, SLOT(makefileParsingStarted()));

    connect(m_makefileParserThread, SIGNAL(finished()),
            this, SLOT(makefileParsingFinished()));
    m_makefileParserThread->start();
}

void AutotoolsProject::makefileParsingStarted()
{
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
}

void AutotoolsProject::makefileParsingFinished()
{
    // The finished() signal is from a previous makefile-parser-thread
    // and can be skipped. This can happen, if the thread has emitted the
    // finished() signal during the execution of AutotoolsProject::loadProjectTree().
    // In this case the signal is in the message queue already and deleting
    // the thread of course does not remove the signal again.
    if (sender() != m_makefileParserThread)
        return;

    QApplication::restoreOverrideCursor();

    if (m_makefileParserThread->isCanceled()) {
        // The parsing has been cancelled by the user. Don't show any
        // project data at all.
        m_makefileParserThread->deleteLater();
        m_makefileParserThread = 0;
        return;
    }

    if (m_makefileParserThread->hasError())
        qWarning("Parsing of makefile contained errors.");

    // Remove file watches for the current project state.
    // The file watches will be added again after the parsing.
    foreach (const QString& watchedFile, m_watchedFiles)
        m_fileWatcher->removeFile(watchedFile);

    m_watchedFiles.clear();

    // Apply sources to m_files, which are returned at AutotoolsProject::files()
    const QFileInfo fileInfo(m_fileName);
    const QDir dir = fileInfo.absoluteDir();
    QStringList files = m_makefileParserThread->sources();
    foreach (const QString& file, files)
        m_files.append(dir.absoluteFilePath(file));

    // Watch for changes of Makefile.am files. If a Makefile.am file
    // has been changed, the project tree must be reparsed.
    const QStringList makefiles = m_makefileParserThread->makefiles();
hjk's avatar
hjk committed
227
    foreach (const QString &makefile, makefiles) {
228 229 230 231 232 233 234
        files.append(makefile);

        const QString watchedFile = dir.absoluteFilePath(makefile);
        m_fileWatcher->addFile(watchedFile, Utils::FileSystemWatcher::WatchAllChanges);
        m_watchedFiles.append(watchedFile);
    }

hjk's avatar
hjk committed
235
    // Add configure.ac file to project and watch for changes.
236
    const QLatin1String configureAc(QLatin1String("configure.ac"));
237
    const QFile configureAcFile(fileInfo.absolutePath() + QLatin1Char('/') + configureAc);
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
    if (configureAcFile.exists()) {
        files.append(configureAc);
        const QString configureAcFilePath = dir.absoluteFilePath(configureAc);
        m_fileWatcher->addFile(configureAcFilePath, Utils::FileSystemWatcher::WatchAllChanges);
        m_watchedFiles.append(configureAcFilePath);
    }

    buildFileNodeTree(dir, files);
    updateCppCodeModel();

    m_makefileParserThread->deleteLater();
    m_makefileParserThread = 0;
}

void AutotoolsProject::onFileChanged(const QString &file)
{
    Q_UNUSED(file);
255
    loadProjectTree();
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
}

QStringList AutotoolsProject::buildTargets() const
{
    QStringList targets;
    targets.append(QLatin1String("all"));
    targets.append(QLatin1String("clean"));
    return targets;
}

void AutotoolsProject::buildFileNodeTree(const QDir &directory,
                                         const QStringList &files)
{
    // Get all existing nodes and remember them in a hash table.
    // This allows to reuse existing nodes and to remove obsolete
    // nodes later.
272 273
    QHash<QString, Node *> nodeHash;
    foreach (Node * node, nodes(m_rootNode))
274 275 276 277 278 279
        nodeHash.insert(node->path(), node);

    // Add the sources to the filenode project tree. Sources
    // inside the same directory are grouped into a folder-node.
    const QString baseDir = directory.absolutePath();

280 281 282
    QList<FileNode *> fileNodes;
    FolderNode *parentFolder = 0;
    FolderNode *oldParentFolder = 0;
283 284

    foreach (const QString& file, files) {
285
        if (file.endsWith(QLatin1String(".moc")))
286 287
            continue;

288 289 290 291
        QString subDir = baseDir + QLatin1Char('/') + file;
        const int lastSlashPos = subDir.lastIndexOf(QLatin1Char('/'));
        if (lastSlashPos != -1)
            subDir.truncate(lastSlashPos);
292 293 294 295 296

        // Add folder nodes, that are not already available
        oldParentFolder = parentFolder;
        parentFolder = 0;
        if (nodeHash.contains(subDir)) {
297 298
            QTC_ASSERT(nodeHash[subDir]->nodeType() == FolderNodeType, return);
            parentFolder = static_cast<FolderNode *>(nodeHash[subDir]);
299 300 301 302 303 304 305 306 307 308 309 310
        } else {
            parentFolder = insertFolderNode(QDir(subDir), nodeHash);
            if (parentFolder == 0) {
                // No node gets created for the root folder
                parentFolder = m_rootNode;
            }
        }
        QTC_ASSERT(parentFolder != 0, return);
        if ((oldParentFolder != parentFolder) && !fileNodes.isEmpty()) {
            // AutotoolsProjectNode::addFileNodes() is a very expensive operation. It is
            // important to collect as much file nodes of the same parent folder as
            // possible before invoking it.
311
            oldParentFolder->addFileNodes(fileNodes);
312 313 314 315 316
            fileNodes.clear();
        }

        // Add file node
        const QString filePath = directory.absoluteFilePath(file);
317
        if (nodeHash.contains(filePath)) {
318
            nodeHash.remove(filePath);
319 320 321 322 323 324
        } else {
            if (file == QLatin1String("Makefile.am") || file == QLatin1String("configure.ac"))
                fileNodes.append(new FileNode(filePath, ProjectFileType, false));
            else
                fileNodes.append(new FileNode(filePath, ResourceType, false));
        }
325 326 327
    }

    if (!fileNodes.isEmpty())
328
        parentFolder->addFileNodes(fileNodes);
329 330

    // Remove unused file nodes and empty folder nodes
331
    QHash<QString, Node *>::const_iterator it = nodeHash.constBegin();
332
    while (it != nodeHash.constEnd()) {
333 334 335
        if ((*it)->nodeType() == FileNodeType) {
            FileNode *fileNode = static_cast<FileNode *>(*it);
            FolderNode* parent = fileNode->parentFolderNode();
336
            parent->removeFileNodes(QList<FileNode *>() << fileNode);
337 338 339

            // Remove all empty parent folders
            while (parent->subFolderNodes().isEmpty() && parent->fileNodes().isEmpty()) {
340
                FolderNode *grandParent = parent->parentFolderNode();
341
                grandParent->removeFolderNodes(QList<FolderNode *>() << parent);
342 343 344 345 346 347 348 349 350
                parent = grandParent;
                if (parent == m_rootNode)
                    break;
            }
        }
        ++it;
    }
}

351
FolderNode *AutotoolsProject::insertFolderNode(const QDir &nodeDir, QHash<QString, Node *> &nodes)
352 353 354 355 356 357 358 359 360
{
    const QString nodePath = nodeDir.absolutePath();
    QFileInfo rootInfo(m_rootNode->path());
    const QString rootPath = rootInfo.absolutePath();

    // Do not create a folder for the root node
    if (rootPath == nodePath)
        return 0;

361
    FolderNode *folder = new FolderNode(nodePath);
362 363 364 365 366
    QDir dir(nodeDir);
    folder->setDisplayName(dir.dirName());

    // Get parent-folder. If it does not exist, create it recursively.
    // Take care that the m_rootNode is considered as top folder.
367
    FolderNode *parentFolder = m_rootNode;
368 369 370
    if ((rootPath != folder->path()) && dir.cdUp()) {
        const QString parentDir = dir.absolutePath();
        if (!nodes.contains(parentDir)) {
371
            FolderNode *insertedFolder = insertFolderNode(parentDir, nodes);
372 373 374
            if (insertedFolder != 0)
                parentFolder = insertedFolder;
        } else {
375 376
            QTC_ASSERT(nodes[parentDir]->nodeType() == FolderNodeType, return 0);
            parentFolder = static_cast<FolderNode *>(nodes[parentDir]);
377 378 379
        }
    }

380
    parentFolder->addFolderNodes(QList<FolderNode *>() << folder);
381 382 383 384 385
    nodes.insert(nodePath, folder);

    return folder;
}

386
QList<Node *> AutotoolsProject::nodes(FolderNode *parent) const
387
{
388
    QList<Node *> list;
389 390
    QTC_ASSERT(parent != 0, return list);

391
    foreach (FolderNode *folder, parent->subFolderNodes()) {
392 393 394
        list.append(nodes(folder));
        list.append(folder);
    }
395
    foreach (FileNode *file, parent->fileNodes())
396 397 398 399 400 401 402
        list.append(file);

    return list;
}

void AutotoolsProject::updateCppCodeModel()
{
403 404
    CppTools::CppModelManager *modelManager =
        CppTools::CppModelManager::instance();
405

406 407 408
    if (!modelManager)
        return;

409 410 411 412 413 414
    m_codeModelFuture.cancel();
    CppTools::ProjectInfo pInfo = modelManager->projectInfo(this);
    pInfo.clearProjectParts();

    CppTools::ProjectPartBuilder ppBuilder(pInfo);

415 416 417 418
    const QStringList cflags = m_makefileParserThread->cflags();
    QStringList cxxflags = m_makefileParserThread->cxxflags();
    if (cxxflags.isEmpty())
        cxxflags = cflags;
419 420
    ppBuilder.setCFlags(cflags);
    ppBuilder.setCxxFlags(cxxflags);
Tobias Hunger's avatar
Tobias Hunger committed
421

422 423
    ppBuilder.setIncludePaths(m_makefileParserThread->includePaths());
    ppBuilder.setDefines(m_makefileParserThread->defines());
424

425 426 427
    const QList<Core::Id> languages = ppBuilder.createProjectPartsForFiles(m_files);
    foreach (Core::Id language, languages)
        setProjectLanguage(language, true);
428

429
    pInfo.finish();
430
    m_codeModelFuture = modelManager->updateProjectInfo(pInfo);
431
}