// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmakemakestep.h" #include "qmakebuildconfiguration.h" #include "qmakenodes.h" #include "qmakeparser.h" #include "qmakeproject.h" #include "qmakeprojectmanagerconstants.h" #include "qmakeprojectmanagertr.h" #include "qmakesettings.h" #include "qmakestep.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace QmakeProjectManager { namespace Internal { class QmakeMakeStep : public MakeStep { public: QmakeMakeStep(BuildStepList *bsl, Id id); private: bool init() override; void setupOutputFormatter(OutputFormatter *formatter) override; Tasking::GroupItem runRecipe() final; QStringList displayArguments() const override; bool m_scriptTarget = false; FilePath m_makeFileToCheck; bool m_unalignedBuildDir; bool m_ignoredNonTopLevelBuild = false; }; QmakeMakeStep::QmakeMakeStep(BuildStepList *bsl, Id id) : MakeStep(bsl, id) { if (bsl->id() == ProjectExplorer::Constants::BUILDSTEPS_CLEAN) { setIgnoreReturnValue(true); setUserArguments("clean"); } supportDisablingForSubdirs(); } bool QmakeMakeStep::init() { // Note: This skips the Makestep::init() level. if (!AbstractProcessStep::init()) return false; const auto bc = static_cast(buildConfiguration()); const CommandLine unmodifiedMake = effectiveMakeCommand(Execution); const FilePath makeExecutable = unmodifiedMake.executable(); if (makeExecutable.isEmpty()) emit addTask(makeCommandMissingTask()); if (!bc || makeExecutable.isEmpty()) { emitFaultyConfigurationMessage(); return false; } // Ignore all but the first make step for a non-top-level build. See QTCREATORBUG-15794. m_ignoredNonTopLevelBuild = (bc->fileNodeBuild() || bc->subNodeBuild()) && !enabledForSubDirs(); ProcessParameters *pp = processParameters(); pp->setMacroExpander(bc->macroExpander()); FilePath workingDirectory; if (bc->subNodeBuild()) workingDirectory = bc->qmakeBuildSystem()->buildDir(bc->subNodeBuild()->filePath()); else workingDirectory = bc->buildDirectory(); pp->setWorkingDirectory(workingDirectory); CommandLine makeCmd(makeExecutable); QmakeProjectManager::QmakeProFileNode *subProFile = bc->subNodeBuild(); if (subProFile) { QString makefile = subProFile->makefile(); if (makefile.isEmpty()) makefile = "Makefile"; // Use Makefile.Debug and Makefile.Release // for file builds, since the rules for that are // only in those files. if (subProFile->isDebugAndRelease() && bc->fileNodeBuild()) { if (buildType() == QmakeBuildConfiguration::Debug) makefile += ".Debug"; else makefile += ".Release"; } if (makefile != "Makefile") makeCmd.addArgs({"-f", makefile}); m_makeFileToCheck = workingDirectory / makefile; } else { FilePath makefile = bc->makefile(); if (!makefile.isEmpty()) { makeCmd.addArgs({"-f", makefile.path()}); m_makeFileToCheck = workingDirectory / makefile.path(); } else { m_makeFileToCheck = workingDirectory / "Makefile"; } } makeCmd.addArgs(unmodifiedMake.arguments(), CommandLine::Raw); if (bc->fileNodeBuild() && subProFile) { QString objectsDir = subProFile->objectsDirectory(); if (objectsDir.isEmpty()) { objectsDir = bc->qmakeBuildSystem()->buildDir(subProFile->filePath()).toUrlishString(); if (subProFile->isDebugAndRelease()) { if (bc->buildType() == QmakeBuildConfiguration::Debug) objectsDir += "/debug"; else objectsDir += "/release"; } } if (subProFile->isObjectParallelToSource()) { const FilePath sourceFileDir = bc->fileNodeBuild()->filePath().parentDir(); const FilePath proFileDir = subProFile->proFile()->sourceDir().canonicalPath(); if (!objectsDir.endsWith('/')) objectsDir += QLatin1Char('/'); objectsDir += sourceFileDir.relativeChildPath(proFileDir).toUrlishString(); objectsDir = QDir::cleanPath(objectsDir); } QString relObjectsDir = QDir(pp->workingDirectory().toUrlishString()) .relativeFilePath(objectsDir); if (relObjectsDir == ".") relObjectsDir.clear(); if (!relObjectsDir.isEmpty()) relObjectsDir += '/'; QString objectFile = relObjectsDir + bc->fileNodeBuild()->filePath().baseName() + subProFile->objectExtension(); makeCmd.addArg(objectFile); } pp->setEnvironment(makeEnvironment()); pp->setCommandLine(makeCmd); auto rootNode = dynamic_cast(project()->rootProjectNode()); QTC_ASSERT(rootNode, return false); m_scriptTarget = rootNode->projectType() == ProjectType::ScriptTemplate; m_unalignedBuildDir = !bc->isBuildDirAtSafeLocation(); // A user doing "make clean" indicates they want a proper rebuild, so make sure to really // execute qmake on the next build. if (stepList()->id() == ProjectExplorer::Constants::BUILDSTEPS_CLEAN) { const auto qmakeStep = bc->qmakeStep(); if (qmakeStep) qmakeStep->setForced(true); } return true; } void QmakeMakeStep::setupOutputFormatter(OutputFormatter *formatter) { formatter->addLineParser(new GnuMakeParser()); Toolchain *tc = ToolchainKitAspect::cxxToolchain(kit()); OutputTaskParser *xcodeBuildParser = nullptr; if (tc && tc->targetAbi().os() == Abi::DarwinOS) { xcodeBuildParser = new XcodebuildParser; formatter->addLineParser(xcodeBuildParser); } QList additionalParsers = kit()->createOutputParsers(); // make may cause qmake to be run, add last to make sure it has a low priority. additionalParsers << new QMakeParser; if (xcodeBuildParser) { for (OutputLineParser * const p : std::as_const(additionalParsers)) p->setRedirectionDetector(xcodeBuildParser); } formatter->addLineParsers(additionalParsers); formatter->addSearchDir(processParameters()->effectiveWorkingDirectory()); AbstractProcessStep::setupOutputFormatter(formatter); } Tasking::GroupItem QmakeMakeStep::runRecipe() { using namespace Tasking; const auto onSetup = [this] { if (m_scriptTarget || m_ignoredNonTopLevelBuild) return SetupResult::StopWithSuccess; if (!m_makeFileToCheck.exists()) { const bool success = ignoreReturnValue(); if (!success) { emit addOutput(Tr::tr("Cannot find Makefile. Check your build settings."), OutputFormat::NormalMessage); } return success ? SetupResult::StopWithSuccess : SetupResult::StopWithError; } return SetupResult::Continue; }; const auto onError = [this] { if (m_unalignedBuildDir && settings().warnAgainstUnalignedBuildDir()) { const QString msg = Tr::tr("The build directory is not at the same level as the source " "directory, which could be the reason for the build failure."); emit addTask(BuildSystemTask(Task::Warning, msg)); } }; return Group { ignoreReturnValue() ? finishAllAndSuccess : stopOnError, onGroupSetup(onSetup), onGroupDone(onError, CallDoneIf::Error), defaultProcessTask() }; } QStringList QmakeMakeStep::displayArguments() const { const auto bc = static_cast(buildConfiguration()); if (bc && !bc->makefile().isEmpty()) return {"-f", bc->makefile().path()}; return {}; } /// // QmakeMakeStepFactory /// QmakeMakeStepFactory::QmakeMakeStepFactory() { registerStep(Constants::MAKESTEP_BS_ID); setSupportedProjectType(Constants::QMAKEPROJECT_ID); setDisplayName(MakeStep::defaultDisplayName()); } } // Internal } // QmakeProjectManager