forked from qt-creator/qt-creator
It looks like MesonProjectParser::m_process may be restarted directly from process' finished() handler - inside MesonProjectParser::processFinished(). In this case the process is directly deleted from its signal handler. Fix it by releasing the old process and deleting it later. Don't pass process reference to the other thread when calling runAsync(). Pass the copy of standard output instead. Change-Id: I163a3cc86fbdbe8a3d9a19c479f96017f5803f76 Reviewed-by: Orgad Shaneh <orgads@gmail.com>
334 lines
12 KiB
C++
334 lines
12 KiB
C++
// Copyright (C) 2020 Alexis Jeandet.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
|
|
|
#include "mesonprojectparser.h"
|
|
|
|
#include "mesoninfoparser.h"
|
|
#include "mesonprojectnodes.h"
|
|
#include "mesontools.h"
|
|
#include "projecttree.h"
|
|
|
|
#include <coreplugin/messagemanager.h>
|
|
|
|
#include <projectexplorer/projectexplorer.h>
|
|
|
|
#include <utils/fileinprojectfinder.h>
|
|
#include <utils/runextensions.h>
|
|
|
|
#include <QStringList>
|
|
#include <QTextStream>
|
|
|
|
#include <optional>
|
|
|
|
namespace MesonProjectManager {
|
|
namespace Internal {
|
|
|
|
struct CompilerArgs
|
|
{
|
|
QStringList args;
|
|
QStringList includePaths;
|
|
ProjectExplorer::Macros macros;
|
|
};
|
|
|
|
inline std::optional<QString> extractValueIfMatches(const QString &arg,
|
|
const QStringList &candidates)
|
|
{
|
|
for (const auto &flag : candidates) {
|
|
if (arg.startsWith(flag))
|
|
return arg.mid(flag.length());
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
inline std::optional<QString> extractInclude(const QString &arg)
|
|
{
|
|
return extractValueIfMatches(arg, {"-I", "/I", "-isystem", "-imsvc", "/imsvc"});
|
|
}
|
|
inline std::optional<ProjectExplorer::Macro> extractMacro(const QString &arg)
|
|
{
|
|
auto define = extractValueIfMatches(arg, {"-D", "/D"});
|
|
if (define)
|
|
return ProjectExplorer::Macro::fromKeyValue(define->toLatin1());
|
|
auto undef = extractValueIfMatches(arg, {"-U", "/U"});
|
|
if (undef)
|
|
return ProjectExplorer::Macro(undef->toLatin1(), ProjectExplorer::MacroType::Undefine);
|
|
return std::nullopt;
|
|
}
|
|
|
|
CompilerArgs splitArgs(const QStringList &args)
|
|
{
|
|
CompilerArgs splited;
|
|
for (const QString &arg : args) {
|
|
auto inc = extractInclude(arg);
|
|
if (inc) {
|
|
splited.includePaths << *inc;
|
|
} else {
|
|
auto macro = extractMacro(arg);
|
|
if (macro) {
|
|
splited.macros << *macro;
|
|
} else {
|
|
splited.args << arg;
|
|
}
|
|
}
|
|
}
|
|
return splited;
|
|
}
|
|
|
|
QStringList toAbsolutePath(const Utils::FilePath &refPath, QStringList &pathList)
|
|
{
|
|
QStringList allAbs;
|
|
std::transform(std::cbegin(pathList),
|
|
std::cend(pathList),
|
|
std::back_inserter(allAbs),
|
|
[refPath](const QString &path) {
|
|
if (Utils::FileUtils::isAbsolutePath(path))
|
|
return path;
|
|
return refPath.pathAppended(path).toString();
|
|
});
|
|
return allAbs;
|
|
}
|
|
|
|
MesonProjectParser::MesonProjectParser(const Utils::Id &meson,
|
|
Utils::Environment env,
|
|
ProjectExplorer::Project *project)
|
|
: m_env{env}
|
|
, m_meson{meson}
|
|
, m_projectName{project->displayName()}
|
|
{
|
|
connect(&m_process, &MesonProcess::finished, this, &MesonProjectParser::processFinished);
|
|
connect(&m_process,
|
|
&MesonProcess::readyReadStandardOutput,
|
|
&m_outputParser,
|
|
&MesonOutputParser::readStdo);
|
|
|
|
// TODO re-think the way all BuildSystem/ProjectParser are tied
|
|
// I take project info here, I also take build and src dir later from
|
|
// functions args.
|
|
auto fileFinder = new Utils::FileInProjectFinder;
|
|
fileFinder->setProjectDirectory(project->projectDirectory());
|
|
fileFinder->setProjectFiles(project->files(ProjectExplorer::Project::AllFiles));
|
|
m_outputParser.setFileFinder(fileFinder);
|
|
}
|
|
|
|
void MesonProjectParser::setMesonTool(const Utils::Id &meson)
|
|
{
|
|
m_meson = meson;
|
|
}
|
|
|
|
bool MesonProjectParser::configure(const Utils::FilePath &sourcePath,
|
|
const Utils::FilePath &buildPath,
|
|
const QStringList &args)
|
|
{
|
|
m_introType = IntroDataType::file;
|
|
m_srcDir = sourcePath;
|
|
m_buildDir = buildPath;
|
|
m_outputParser.setSourceDirectory(sourcePath);
|
|
auto cmd = MesonTools::mesonWrapper(m_meson)->configure(sourcePath, buildPath, args);
|
|
// see comment near m_pendingCommands declaration
|
|
m_pendingCommands.enqueue(
|
|
std::make_tuple(MesonTools::mesonWrapper(m_meson)->regenerate(sourcePath, buildPath),
|
|
false));
|
|
return m_process.run(cmd, m_env, m_projectName);
|
|
}
|
|
|
|
bool MesonProjectParser::wipe(const Utils::FilePath &sourcePath,
|
|
const Utils::FilePath &buildPath,
|
|
const QStringList &args)
|
|
{
|
|
return setup(sourcePath, buildPath, args, true);
|
|
}
|
|
|
|
bool MesonProjectParser::setup(const Utils::FilePath &sourcePath,
|
|
const Utils::FilePath &buildPath,
|
|
const QStringList &args,
|
|
bool forceWipe)
|
|
{
|
|
m_introType = IntroDataType::file;
|
|
m_srcDir = sourcePath;
|
|
m_buildDir = buildPath;
|
|
m_outputParser.setSourceDirectory(sourcePath);
|
|
auto cmdArgs = args;
|
|
if (forceWipe || isSetup(buildPath))
|
|
cmdArgs << "--wipe";
|
|
auto cmd = MesonTools::mesonWrapper(m_meson)->setup(sourcePath, buildPath, cmdArgs);
|
|
return m_process.run(cmd, m_env, m_projectName);
|
|
}
|
|
|
|
bool MesonProjectParser::parse(const Utils::FilePath &sourcePath, const Utils::FilePath &buildPath)
|
|
{
|
|
m_srcDir = sourcePath;
|
|
m_buildDir = buildPath;
|
|
m_outputParser.setSourceDirectory(sourcePath);
|
|
if (!isSetup(buildPath)) {
|
|
return parse(sourcePath);
|
|
} else {
|
|
m_introType = IntroDataType::file;
|
|
return startParser();
|
|
}
|
|
}
|
|
|
|
bool MesonProjectParser::parse(const Utils::FilePath &sourcePath)
|
|
{
|
|
m_srcDir = sourcePath;
|
|
m_introType = IntroDataType::stdo;
|
|
m_outputParser.setSourceDirectory(sourcePath);
|
|
return m_process.run(MesonTools::mesonWrapper(m_meson)->introspect(sourcePath),
|
|
m_env,
|
|
m_projectName,
|
|
true);
|
|
}
|
|
|
|
QList<ProjectExplorer::BuildTargetInfo> MesonProjectParser::appsTargets() const
|
|
{
|
|
QList<ProjectExplorer::BuildTargetInfo> apps;
|
|
for (const Target &target : m_parserResult.targets) {
|
|
if (target.type == Target::Type::executable) {
|
|
ProjectExplorer::BuildTargetInfo bti;
|
|
bti.displayName = target.name;
|
|
bti.buildKey = Target::fullName(m_srcDir, target);
|
|
bti.displayNameUniquifier = bti.buildKey;
|
|
bti.targetFilePath = Utils::FilePath::fromString(target.fileName.first());
|
|
bti.workingDirectory = Utils::FilePath::fromString(target.fileName.first()).absolutePath();
|
|
bti.projectFilePath = Utils::FilePath::fromString(target.definedIn);
|
|
bti.usesTerminal = true;
|
|
apps.append(bti);
|
|
}
|
|
}
|
|
return apps;
|
|
}
|
|
|
|
bool MesonProjectParser::startParser()
|
|
{
|
|
m_parserFutureResult = Utils::runAsync(
|
|
ProjectExplorer::ProjectExplorerPlugin::sharedThreadPool(),
|
|
[processOutput = m_process.stdOut(), introType = m_introType,
|
|
buildDir = m_buildDir.toString(), srcDir = m_srcDir] {
|
|
if (introType == IntroDataType::file)
|
|
return extractParserResults(srcDir, MesonInfoParser::parse(buildDir));
|
|
else
|
|
return extractParserResults(srcDir, MesonInfoParser::parse(processOutput));
|
|
});
|
|
|
|
Utils::onFinished(m_parserFutureResult, this, &MesonProjectParser::update);
|
|
return true;
|
|
}
|
|
|
|
MesonProjectParser::ParserData *MesonProjectParser::extractParserResults(
|
|
const Utils::FilePath &srcDir, MesonInfoParser::Result &&parserResult)
|
|
{
|
|
auto rootNode = ProjectTree::buildTree(srcDir,
|
|
parserResult.targets,
|
|
parserResult.buildSystemFiles);
|
|
return new ParserData{std::move(parserResult), std::move(rootNode)};
|
|
}
|
|
|
|
void MesonProjectParser::addMissingTargets(QStringList &targetList)
|
|
{
|
|
// Not all targets are listed in introspection data
|
|
for (const auto &target : additionalTargets()) {
|
|
if (!targetList.contains(target)) {
|
|
targetList.append(target);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MesonProjectParser::update(const QFuture<MesonProjectParser::ParserData *> &data)
|
|
{
|
|
auto parserData = data.result();
|
|
m_parserResult = std::move(parserData->data);
|
|
m_rootNode = std::move(parserData->rootNode);
|
|
m_targetsNames.clear();
|
|
for (const Target &target : m_parserResult.targets) {
|
|
m_targetsNames.push_back(Target::fullName(m_srcDir, target));
|
|
}
|
|
addMissingTargets(m_targetsNames);
|
|
m_targetsNames.sort();
|
|
delete parserData;
|
|
emit parsingCompleted(true);
|
|
}
|
|
|
|
ProjectExplorer::RawProjectPart MesonProjectParser::buildRawPart(
|
|
const Target &target,
|
|
const Target::SourceGroup &sources,
|
|
const ProjectExplorer::ToolChain *cxxToolChain,
|
|
const ProjectExplorer::ToolChain *cToolChain)
|
|
{
|
|
ProjectExplorer::RawProjectPart part;
|
|
part.setDisplayName(target.name);
|
|
part.setBuildSystemTarget(Target::fullName(m_srcDir, target));
|
|
part.setFiles(sources.sources + sources.generatedSources);
|
|
auto flags = splitArgs(sources.parameters);
|
|
part.setMacros(flags.macros);
|
|
part.setIncludePaths(toAbsolutePath(m_buildDir, flags.includePaths));
|
|
part.setProjectFileLocation(target.definedIn);
|
|
if (sources.language == "cpp")
|
|
part.setFlagsForCxx({cxxToolChain, flags.args, {}});
|
|
else if (sources.language == "c")
|
|
part.setFlagsForC({cToolChain, flags.args, {}});
|
|
part.setQtVersion(m_qtVersion);
|
|
return part;
|
|
}
|
|
|
|
void MesonProjectParser::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
|
{
|
|
if (exitCode == 0 && exitStatus == QProcess::NormalExit) {
|
|
if (m_pendingCommands.isEmpty())
|
|
startParser();
|
|
else {
|
|
// see comment near m_pendingCommands declaration
|
|
std::tuple<Command, bool> args = m_pendingCommands.dequeue();
|
|
m_process.run(std::get<0>(args), m_env, m_projectName, std::get<1>(args));
|
|
}
|
|
} else {
|
|
if (m_introType == IntroDataType::stdo) {
|
|
auto data = m_process.stdErr();
|
|
Core::MessageManager::writeSilently(QString::fromLocal8Bit(data));
|
|
m_outputParser.readStdo(data);
|
|
}
|
|
emit parsingCompleted(false);
|
|
}
|
|
}
|
|
|
|
ProjectExplorer::RawProjectParts MesonProjectParser::buildProjectParts(
|
|
const ProjectExplorer::ToolChain *cxxToolChain, const ProjectExplorer::ToolChain *cToolChain)
|
|
{
|
|
ProjectExplorer::RawProjectParts parts;
|
|
for_each_source_group(m_parserResult.targets,
|
|
[&parts,
|
|
&cxxToolChain,
|
|
&cToolChain,
|
|
this](const Target &target, const Target::SourceGroup &sourceList) {
|
|
parts.push_back(
|
|
buildRawPart(target, sourceList, cxxToolChain, cToolChain));
|
|
});
|
|
return parts;
|
|
}
|
|
|
|
bool sourceGroupMatchesKit(const KitData &kit, const Target::SourceGroup &group)
|
|
{
|
|
if (group.language == "c")
|
|
return kit.cCompilerPath == group.compiler[0];
|
|
if (group.language == "cpp")
|
|
return kit.cxxCompilerPath == group.compiler[0];
|
|
return true;
|
|
}
|
|
|
|
bool MesonProjectParser::matchesKit(const KitData &kit)
|
|
{
|
|
bool matches = true;
|
|
for_each_source_group(m_parserResult.targets,
|
|
[&matches, &kit](const Target &, const Target::SourceGroup &sourceGroup) {
|
|
matches = matches && sourceGroupMatchesKit(kit, sourceGroup);
|
|
});
|
|
return matches;
|
|
}
|
|
|
|
bool MesonProjectParser::usesSameMesonVersion(const Utils::FilePath &buildPath)
|
|
{
|
|
auto info = MesonInfoParser::mesonInfo(buildPath.toString());
|
|
auto meson = MesonTools::mesonWrapper(m_meson);
|
|
return info && meson && info->mesonVersion == meson->version();
|
|
}
|
|
} // namespace Internal
|
|
} // namespace MesonProjectManager
|