/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** 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 The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "builddirmanager.h" #include "cmakekitinformation.h" #include "cmakeparser.h" #include "cmakeprojectmanager.h" #include "cmaketool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // -------------------------------------------------------------------- // Helper: // -------------------------------------------------------------------- namespace CMakeProjectManager { namespace Internal { static QStringList toArguments(const CMakeConfig &config) { return Utils::transform(config, [](const CMakeConfigItem &i) -> QString { QString a = QString::fromLatin1("-D"); a.append(QString::fromUtf8(i.key)); switch (i.type) { case CMakeConfigItem::FILEPATH: a.append(QLatin1String(":FILEPATH=")); break; case CMakeConfigItem::PATH: a.append(QLatin1String(":PATH=")); break; case CMakeConfigItem::BOOL: a.append(QLatin1String(":BOOL=")); break; case CMakeConfigItem::STRING: a.append(QLatin1String(":STRING=")); break; case CMakeConfigItem::INTERNAL: a.append(QLatin1String(":INTERNAL=")); break; } a.append(QString::fromUtf8(i.value)); return a; }); } // -------------------------------------------------------------------- // BuildDirManager: // -------------------------------------------------------------------- BuildDirManager::BuildDirManager(const Utils::FileName &sourceDir, const ProjectExplorer::Kit *k, const CMakeConfig &inputConfig, const Utils::Environment &env, const Utils::FileName &buildDir) : m_sourceDir(sourceDir), m_buildDir(buildDir), m_kit(k), m_environment(env), m_inputConfig(inputConfig), m_watcher(new QFileSystemWatcher(this)) { QTC_CHECK(!sourceDir.isEmpty()); m_projectName = m_sourceDir.fileName(); if (m_buildDir.isEmpty()) { m_tempDir = new QTemporaryDir(QLatin1String("cmake-tmp-XXXXXX")); m_buildDir = Utils::FileName::fromString(m_tempDir->path()); } QTC_CHECK(!m_buildDir.isEmpty()); QTC_CHECK(k); connect(m_watcher, &QFileSystemWatcher::fileChanged, this, [this]() { if (!isBusy()) forceReparse(); }); QTimer::singleShot(0, this, &BuildDirManager::parse); } BuildDirManager::~BuildDirManager() { delete m_tempDir; } bool BuildDirManager::isBusy() const { if (m_cmakeProcess) return m_cmakeProcess->state() != QProcess::NotRunning; return false; } void BuildDirManager::forceReparse() { CMakeTool *tool = CMakeKitInformation::cmakeTool(m_kit); const QString generator = CMakeGeneratorKitInformation::generator(m_kit); QTC_ASSERT(tool, return); QTC_ASSERT(!generator.isEmpty(), return); startCMake(tool, generator, m_inputConfig); } void BuildDirManager::parse() { CMakeTool *tool = CMakeKitInformation::cmakeTool(m_kit); const QString generator = CMakeGeneratorKitInformation::generator(m_kit); QTC_ASSERT(tool, return); QTC_ASSERT(!generator.isEmpty(), return); // Pop up a dialog asking the user to rerun cmake QString cbpFile = CMakeManager::findCbpFile(QDir(m_buildDir.toString())); QFileInfo cbpFileFi(cbpFile); if (!cbpFileFi.exists()) { // Initial create: startCMake(tool, generator, m_inputConfig); return; } const bool mustUpdate = Utils::anyOf(m_watchedFiles, [&cbpFileFi](const Utils::FileName &f) { return f.toFileInfo().lastModified() > cbpFileFi.lastModified(); }); if (mustUpdate) { startCMake(tool, generator, CMakeConfig()); } else { extractData(); emit dataAvailable(); } } bool BuildDirManager::isProjectFile(const Utils::FileName &fileName) const { return m_watchedFiles.contains(fileName); } QString BuildDirManager::projectName() const { return m_projectName; } QList BuildDirManager::buildTargets() const { return m_buildTargets; } QList BuildDirManager::files() const { return m_files; } void BuildDirManager::extractData() { const Utils::FileName topCMake = Utils::FileName::fromString(m_sourceDir.toString() + QLatin1String("/CMakeLists.txt")); m_projectName = m_sourceDir.fileName(); m_buildTargets.clear(); m_watchedFiles.clear(); m_files.clear(); m_files.append(new ProjectExplorer::FileNode(topCMake, ProjectExplorer::ProjectFileType, false)); m_watchedFiles.insert(topCMake); foreach (const QString &file, m_watcher->files()) m_watcher->removePath(file); // Find cbp file QString cbpFile = CMakeManager::findCbpFile(m_buildDir.toString()); if (cbpFile.isEmpty()) return; m_watcher->addPath(cbpFile); // setFolderName CMakeCbpParser cbpparser; // Parsing if (!cbpparser.parseCbpFile(m_kit, cbpFile, m_sourceDir.toString())) return; m_projectName = cbpparser.projectName(); m_files = cbpparser.fileList(); QSet projectFiles; if (cbpparser.hasCMakeFiles()) { m_files.append(cbpparser.cmakeFileList()); foreach (const ProjectExplorer::FileNode *node, cbpparser.cmakeFileList()) projectFiles.insert(node->filePath()); } else { m_files.append(new ProjectExplorer::FileNode(topCMake, ProjectExplorer::ProjectFileType, false)); projectFiles.insert(topCMake); } m_watchedFiles = projectFiles; foreach (const Utils::FileName &f, m_watchedFiles) m_watcher->addPath(f.toString()); m_buildTargets = cbpparser.buildTargets(); } void BuildDirManager::startCMake(CMakeTool *tool, const QString &generator, const CMakeConfig &config) { QTC_ASSERT(tool && tool->isValid(), return); QTC_ASSERT(!m_cmakeProcess, return); QTC_ASSERT(!m_parser, return); // Make sure m_buildDir exists: const QString buildDirStr = m_buildDir.toString(); QDir bDir = QDir(buildDirStr); bDir.mkpath(buildDirStr); m_parser = new CMakeParser; QDir source = QDir(m_sourceDir.toString()); connect(m_parser, &ProjectExplorer::IOutputParser::addTask, m_parser, [source](const ProjectExplorer::Task &task) { if (task.file.isEmpty() || task.file.toFileInfo().isAbsolute()) { ProjectExplorer::TaskHub::addTask(task); } else { ProjectExplorer::Task t = task; t.file = Utils::FileName::fromString(source.absoluteFilePath(task.file.toString())); ProjectExplorer::TaskHub::addTask(t); } }); // Always use the sourceDir: If we are triggered because the build directory is getting deleted // then we are racing against CMakeCache.txt also getting deleted. const QString srcDir = m_sourceDir.toString(); m_cmakeProcess = new Utils::QtcProcess(this); m_cmakeProcess->setWorkingDirectory(buildDirStr); m_cmakeProcess->setEnvironment(m_environment); connect(m_cmakeProcess, &QProcess::readyReadStandardOutput, this, &BuildDirManager::processCMakeOutput); connect(m_cmakeProcess, &QProcess::readyReadStandardError, this, &BuildDirManager::processCMakeError); connect(m_cmakeProcess, static_cast(&QProcess::finished), this, &BuildDirManager::cmakeFinished); QString args; Utils::QtcProcess::addArg(&args, srcDir); if (!generator.isEmpty()) Utils::QtcProcess::addArg(&args, QString::fromLatin1("-G%1").arg(generator)); Utils::QtcProcess::addArgs(&args, toArguments(config)); ProjectExplorer::TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); m_cmakeProcess->setCommand(tool->cmakeExecutable().toString(), args); m_cmakeProcess->start(); emit parsingStarted(); } void BuildDirManager::cmakeFinished(int code, QProcess::ExitStatus status) { QTC_ASSERT(m_cmakeProcess, return); // process rest of the output: processCMakeOutput(); processCMakeError(); m_parser->flush(); delete m_parser; m_parser = nullptr; m_cmakeProcess->deleteLater(); m_cmakeProcess = nullptr; extractData(); // try even if cmake failed... QString msg; if (status != QProcess::NormalExit) msg = tr("*** cmake process crashed!"); else if (code != 0) msg = tr("*** cmake process exited with exit code %1.").arg(code); if (!msg.isEmpty()) { Core::MessageManager::write(msg); ProjectExplorer::TaskHub::addTask(ProjectExplorer::Task::Error, msg, ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); } emit dataAvailable(); } static QString lineSplit(const QString &rest, const QByteArray &array, std::function f) { QString tmp = rest + Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(array)); int start = 0; int end = tmp.indexOf(QLatin1Char('\n'), start); while (end >= 0) { f(tmp.mid(start, end - start)); start = end + 1; end = tmp.indexOf(QLatin1Char('\n'), start); } return tmp.mid(start); } void BuildDirManager::processCMakeOutput() { static QString rest; rest = lineSplit(rest, m_cmakeProcess->readAllStandardOutput(), [this](const QString &s) { Core::MessageManager::write(s); }); } void BuildDirManager::processCMakeError() { static QString rest; rest = lineSplit(rest, m_cmakeProcess->readAllStandardError(), [this](const QString &s) { m_parser->stdError(s); Core::MessageManager::write(s); }); } } // namespace Internal } // namespace CMakeProjectManager