// Copyright (C) 2020 Alexis Jeandet. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "mesonprojectparser.h" #include "mesoninfoparser.h" #include "mesonprojectnodes.h" #include "mesontools.h" #include "projecttree.h" #include #include #include #include #include #include #include namespace MesonProjectManager { namespace Internal { struct CompilerArgs { QStringList args; QStringList includePaths; ProjectExplorer::Macros macros; }; inline std::optional 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 extractInclude(const QString &arg) { return extractValueIfMatches(arg, {"-I", "/I", "-isystem", "-imsvc", "/imsvc"}); } inline std::optional 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) { return refPath.resolvePath(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 MesonProjectParser::appsTargets() const { QList 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::asyncRun( 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 &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 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