autotoolsproject.cpp 16 KB
Newer Older
1 2
/**************************************************************************
**
3
** Copyright (C) 2013 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/ModelManagerInterface.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
{
    setProjectContext(Core::Context(Constants::PROJECT_CONTEXT));
82 83 84
    Core::Context pl(ProjectExplorer::Constants::LANG_CXX);
    pl.add(ProjectExplorer::Constants::LANG_QMLJS);
    setProjectLanguages(pl);
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 112

    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;
}

113
Core::Id AutotoolsProject::id() const
114
{
115
    return Core::Id(Constants::AUTOTOOLS_PROJECT_ID);
116 117
}

118
Core::IDocument *AutotoolsProject::document() const
119 120 121 122
{
    return m_file;
}

123
IProjectManager *AutotoolsProject::projectManager() const
124 125 126 127 128 129
{
    return m_manager;
}

QString AutotoolsProject::defaultBuildDirectory() const
{
130
    return projectDirectory();
131 132
}

133
ProjectNode *AutotoolsProject::rootProjectNode() const
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
{
    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.
155
    loadProjectTree();
Tobias Hunger's avatar
Tobias Hunger committed
156

Tobias Hunger's avatar
Tobias Hunger committed
157 158 159
    Kit *defaultKit = KitManager::instance()->defaultKit();
    if (!activeTarget() && defaultKit)
        addTarget(createTarget(defaultKit));
160 161 162 163

    return true;
}

164
void AutotoolsProject::loadProjectTree()
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 227 228 229 230 231 232
{
    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
233
    foreach (const QString &makefile, makefiles) {
234 235 236 237 238 239 240
        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
241
    // Add configure.ac file to project and watch for changes.
242
    const QLatin1String configureAc(QLatin1String("configure.ac"));
243
    const QFile configureAcFile(fileInfo.absolutePath() + QLatin1Char('/') + configureAc);
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
    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);
261
    loadProjectTree();
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
}

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.
278 279
    QHash<QString, Node *> nodeHash;
    foreach (Node * node, nodes(m_rootNode))
280 281 282 283 284 285
        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();

286 287 288
    QList<FileNode *> fileNodes;
    FolderNode *parentFolder = 0;
    FolderNode *oldParentFolder = 0;
289 290

    foreach (const QString& file, files) {
291
        if (file.endsWith(QLatin1String(".moc")))
292 293
            continue;

294 295 296 297
        QString subDir = baseDir + QLatin1Char('/') + file;
        const int lastSlashPos = subDir.lastIndexOf(QLatin1Char('/'));
        if (lastSlashPos != -1)
            subDir.truncate(lastSlashPos);
298 299 300 301 302

        // Add folder nodes, that are not already available
        oldParentFolder = parentFolder;
        parentFolder = 0;
        if (nodeHash.contains(subDir)) {
303 304
            QTC_ASSERT(nodeHash[subDir]->nodeType() == FolderNodeType, return);
            parentFolder = static_cast<FolderNode *>(nodeHash[subDir]);
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
        } 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.
            m_rootNode->addFileNodes(fileNodes, oldParentFolder);
            fileNodes.clear();
        }

        // Add file node
        const QString filePath = directory.absoluteFilePath(file);
323
        if (nodeHash.contains(filePath)) {
324
            nodeHash.remove(filePath);
325 326 327 328 329 330
        } 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));
        }
331 332 333 334 335 336
    }

    if (!fileNodes.isEmpty())
        m_rootNode->addFileNodes(fileNodes, parentFolder);

    // Remove unused file nodes and empty folder nodes
337
    QHash<QString, Node *>::const_iterator it = nodeHash.constBegin();
338
    while (it != nodeHash.constEnd()) {
339 340 341 342
        if ((*it)->nodeType() == FileNodeType) {
            FileNode *fileNode = static_cast<FileNode *>(*it);
            FolderNode* parent = fileNode->parentFolderNode();
            m_rootNode->removeFileNodes(QList<FileNode *>() << fileNode, parent);
343 344 345

            // Remove all empty parent folders
            while (parent->subFolderNodes().isEmpty() && parent->fileNodes().isEmpty()) {
346 347
                FolderNode *grandParent = parent->parentFolderNode();
                m_rootNode->removeFolderNodes(QList<FolderNode *>() << parent, grandParent);
348 349 350 351 352 353 354 355 356
                parent = grandParent;
                if (parent == m_rootNode)
                    break;
            }
        }
        ++it;
    }
}

357
FolderNode *AutotoolsProject::insertFolderNode(const QDir &nodeDir, QHash<QString, Node *> &nodes)
358 359 360 361 362 363 364 365 366
{
    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;

367
    FolderNode *folder = new FolderNode(nodePath);
368 369 370 371 372
    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.
373
    FolderNode *parentFolder = m_rootNode;
374 375 376
    if ((rootPath != folder->path()) && dir.cdUp()) {
        const QString parentDir = dir.absolutePath();
        if (!nodes.contains(parentDir)) {
377
            FolderNode *insertedFolder = insertFolderNode(parentDir, nodes);
378 379 380
            if (insertedFolder != 0)
                parentFolder = insertedFolder;
        } else {
381 382
            QTC_ASSERT(nodes[parentDir]->nodeType() == FolderNodeType, return 0);
            parentFolder = static_cast<FolderNode *>(nodes[parentDir]);
383 384 385
        }
    }

386
    m_rootNode->addFolderNodes(QList<FolderNode *>() << folder, parentFolder);
387 388 389 390 391
    nodes.insert(nodePath, folder);

    return folder;
}

392
QList<Node *> AutotoolsProject::nodes(FolderNode *parent) const
393
{
394
    QList<Node *> list;
395 396
    QTC_ASSERT(parent != 0, return list);

397
    foreach (FolderNode *folder, parent->subFolderNodes()) {
398 399 400
        list.append(nodes(folder));
        list.append(folder);
    }
401
    foreach (FileNode *file, parent->fileNodes())
402 403 404 405 406 407 408
        list.append(file);

    return list;
}

void AutotoolsProject::updateCppCodeModel()
{
409 410
    CppTools::CppModelManagerInterface *modelManager =
        CppTools::CppModelManagerInterface::instance();
411 412 413 414 415 416

    if (!modelManager)
        return;

    QStringList allIncludePaths = m_makefileParserThread->includePaths();
    QStringList allFrameworkPaths;
Tobias Hunger's avatar
Tobias Hunger committed
417 418
    QByteArray macros;

419 420
    QStringList cxxflags; // FIXME: Autotools should be able to do better than this!

Tobias Hunger's avatar
Tobias Hunger committed
421
    if (activeTarget()) {
422 423
        ProjectExplorer::Kit *k = activeTarget()->kit();
        ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(k);
Tobias Hunger's avatar
Tobias Hunger committed
424
        if (tc) {
425 426
            const QList<HeaderPath> allHeaderPaths = tc->systemHeaderPaths(cxxflags,
                                                                           SysRootKitInformation::sysRoot(k));
Tobias Hunger's avatar
Tobias Hunger committed
427 428 429 430 431
            foreach (const HeaderPath &headerPath, allHeaderPaths) {
                if (headerPath.kind() == HeaderPath::FrameworkHeaderPath)
                    allFrameworkPaths.append(headerPath.path());
                else
                    allIncludePaths.append(headerPath.path());
432
            }
433
            macros = tc->predefinedMacros(cxxflags);
Tobias Hunger's avatar
Tobias Hunger committed
434
            macros += '\n';
435 436 437
        }
    }

438
    CppTools::CppModelManagerInterface::ProjectInfo pinfo = modelManager->projectInfo(this);
439

440 441
    const bool update = (pinfo.includePaths() != allIncludePaths)
            || (pinfo.sourceFiles() != m_files)
Tobias Hunger's avatar
Tobias Hunger committed
442
            || (pinfo.defines() != macros)
443
            || (pinfo.frameworkPaths() != allFrameworkPaths);
444
    if (update) {
445
        pinfo.clearProjectParts();
446
        CppTools::ProjectPart::Ptr part(new CppTools::ProjectPart);
447
        part->includePaths = allIncludePaths;
448
        foreach (const QString &file, m_files)
449
            part->files << CppTools::ProjectFile(file, CppTools::ProjectFile::CXXSource);
450

Tobias Hunger's avatar
Tobias Hunger committed
451
        part->defines = macros;
452
        part->frameworkPaths = allFrameworkPaths;
453 454
        part->cVersion = CppTools::ProjectPart::C99;
        part->cxxVersion = CppTools::ProjectPart::CXX11;
455 456
        pinfo.appendProjectPart(part);

457
        modelManager->updateProjectInfo(pinfo);
458
        modelManager->updateSourceFiles(m_files);
459 460
    }
}