// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "makestep.h" #include "buildconfiguration.h" #include "buildsystem.h" #include "devicesupport/devicekitaspects.h" #include "devicesupport/idevice.h" #include "gnumakeparser.h" #include "kit.h" #include "processparameters.h" #include "projectexplorerconstants.h" #include "projectexplorersettings.h" #include "projectexplorertr.h" #include "target.h" #include "toolchain.h" #include "toolchainkitaspect.h" #include #include #include #include #include #include #include #include #include using namespace Core; using namespace Utils; const char BUILD_TARGETS_SUFFIX[] = ".BuildTargets"; const char MAKE_ARGUMENTS_SUFFIX[] = ".MakeArguments"; const char MAKE_COMMAND_SUFFIX[] = ".MakeCommand"; const char OVERRIDE_MAKEFLAGS_SUFFIX[] = ".OverrideMakeflags"; const char JOBCOUNT_SUFFIX[] = ".JobCount"; const char MAKEFLAGS[] = "MAKEFLAGS"; namespace ProjectExplorer { MakeStep::MakeStep(BuildStepList *parent, Id id) : AbstractProcessStep(parent, id) { setLowPriority(); setCommandLineProvider([this] { return effectiveMakeCommand(Execution); }); // FIXME: Replace with id.name() + MAKE_COMMAND_SUFFIX after the Key/Store transition m_makeCommandAspect.setSettingsKey(id.toKey() + MAKE_COMMAND_SUFFIX); m_makeCommandAspect.setExpectedKind(PathChooser::ExistingCommand); m_makeCommandAspect.setBaseDirectory(PathChooser::homePath()); m_makeCommandAspect.setHistoryCompleter("PE.MakeCommand.History"); m_userArgumentsAspect.setSettingsKey(id.toKey() + MAKE_ARGUMENTS_SUFFIX); m_userArgumentsAspect.setLabelText(Tr::tr("Make arguments:")); m_userArgumentsAspect.setDisplayStyle(StringAspect::LineEditDisplay); m_jobCountAspect.setSettingsKey(id.toKey() + JOBCOUNT_SUFFIX); m_jobCountAspect.setLabel(Tr::tr("Parallel jobs:")); m_jobCountAspect.setRange(1, 999); m_jobCountAspect.setValue(defaultJobCount()); m_jobCountAspect.setDefaultValue(defaultJobCount()); const QString text = Tr::tr("Override MAKEFLAGS"); m_overrideMakeflagsAspect.setSettingsKey(id.toKey() + OVERRIDE_MAKEFLAGS_SUFFIX); m_overrideMakeflagsAspect.setLabel(text, BoolAspect::LabelPlacement::AtCheckBox); m_disabledForSubdirsAspect.setSettingsKey(id.toKey() + ".disabledForSubdirs"); m_disabledForSubdirsAspect.setLabel(Tr::tr("Disable in subdirectories:")); m_disabledForSubdirsAspect.setToolTip(Tr::tr("Runs this step only for a top-level build.")); m_buildTargetsAspect.setSettingsKey(id.toKey() + BUILD_TARGETS_SUFFIX); m_buildTargetsAspect.setLabelText(Tr::tr("Targets:")); const auto updateMakeLabel = [this] { const FilePath defaultMake = defaultMakeCommand(); const QString labelText = defaultMake.isEmpty() ? Tr::tr("Make:") : Tr::tr("Override %1:").arg(defaultMake.toUserOutput()); m_makeCommandAspect.setLabelText(labelText); }; updateMakeLabel(); connect(&m_makeCommandAspect, &StringAspect::changed, this, updateMakeLabel); } void MakeStep::setSelectedBuildTarget(const QString &buildTarget) { m_buildTargetsAspect.setValue({buildTarget}); } void MakeStep::setAvailableBuildTargets(const QStringList &buildTargets) { m_buildTargetsAspect.setAllValues(buildTargets); } bool MakeStep::init() { if (!AbstractProcessStep::init()) return false; const CommandLine make = effectiveMakeCommand(Execution); if (make.executable().isEmpty()) emit addTask(makeCommandMissingTask()); if (make.executable().isEmpty()) { emitFaultyConfigurationMessage(); return false; } return true; } void MakeStep::setupOutputFormatter(OutputFormatter *formatter) { formatter->addLineParser(new GnuMakeParser()); formatter->addLineParsers(kit()->createOutputParsers()); formatter->addSearchDir(processParameters()->effectiveWorkingDirectory()); AbstractProcessStep::setupOutputFormatter(formatter); } QString MakeStep::defaultDisplayName() { return Tr::tr("Make"); } static const QList preferredToolchains(const Kit *kit) { // prefer CXX, then C, then others return Utils::sorted(ToolchainKitAspect::toolChains(kit), [](Toolchain *tcA, Toolchain *tcB) { if (tcA->language() == tcB->language()) return false; if (tcA->language() == Constants::CXX_LANGUAGE_ID) return true; if (tcB->language() == Constants::CXX_LANGUAGE_ID) return false; if (tcA->language() == Constants::C_LANGUAGE_ID) return true; return false; }); } FilePath MakeStep::defaultMakeCommand() const { const Environment env = makeEnvironment(); for (const Toolchain *tc : preferredToolchains(kit())) { FilePath make = tc->makeCommand(env); if (!make.isEmpty()) { IDevice::ConstPtr dev = BuildDeviceKitAspect::device(kit()); QTC_ASSERT(dev, return {}); return dev->filePath(make.path()); } } return {}; } QString MakeStep::msgNoMakeCommand() { return Tr::tr("Make command missing. Specify Make command in step configuration."); } Task MakeStep::makeCommandMissingTask() { return BuildSystemTask(Task::Error, msgNoMakeCommand()); } bool MakeStep::isJobCountSupported() const { const QList tcs = preferredToolchains(kit()); const Toolchain *tc = tcs.isEmpty() ? nullptr : tcs.constFirst(); return tc && tc->isJobCountSupported(); } bool MakeStep::jobCountOverridesMakeflags() const { return m_overrideMakeflagsAspect(); } static std::optional argsJobCount(const QString &str) { const QStringList args = ProcessArgs::splitArgs(str, HostOsInfo::hostOs()); const int argIndex = Utils::indexOf(args, [](const QString &arg) { return arg.startsWith("-j"); }); if (argIndex == -1) return std::nullopt; QString arg = args.at(argIndex); bool requireNumber = false; // -j [4] as separate arguments (or no value) if (arg == "-j") { if (args.size() <= argIndex + 1) return 1000; // unlimited arg = args.at(argIndex + 1); } else { // -j4 arg = arg.mid(2).trimmed(); requireNumber = true; } bool ok = false; const int res = arg.toInt(&ok); if (!ok && requireNumber) return std::nullopt; return std::make_optional(ok && res > 0 ? res : 1000); } bool MakeStep::makeflagsJobCountMismatch() const { const Environment env = makeEnvironment(); if (!env.hasKey(MAKEFLAGS)) return false; std::optional makeFlagsJobCount = argsJobCount(env.expandedValueForKey(MAKEFLAGS)); return makeFlagsJobCount.has_value() && *makeFlagsJobCount != m_jobCountAspect(); } bool MakeStep::enabledForSubDirs() const { return !m_disabledForSubdirsAspect(); } bool MakeStep::makeflagsContainsJobCount() const { const Environment env = makeEnvironment(); if (!env.hasKey(MAKEFLAGS)) return false; return argsJobCount(env.expandedValueForKey(MAKEFLAGS)).has_value(); } bool MakeStep::userArgsContainsJobCount() const { return argsJobCount(userArguments()).has_value(); } Environment MakeStep::makeEnvironment() const { Environment env = buildEnvironment(); env.setupEnglishOutput(); if (makeCommand().isEmpty()) { // We also prepend "L" to the MAKEFLAGS, so that nmake / jom are less verbose const QList tcs = preferredToolchains(kit()); const Toolchain *tc = tcs.isEmpty() ? nullptr : tcs.constFirst(); if (tc && tc->targetAbi().os() == Abi::WindowsOS && tc->targetAbi().osFlavor() != Abi::WindowsMSysFlavor) { env.set(MAKEFLAGS, 'L' + env.expandedValueForKey(MAKEFLAGS)); } } return env; } void MakeStep::setMakeCommand(const FilePath &command) { m_makeCommandAspect.setValue(command); } int MakeStep::defaultJobCount() { return QThread::idealThreadCount(); } QStringList MakeStep::jobArguments() const { if (!isJobCountSupported() || userArgsContainsJobCount() || (makeflagsContainsJobCount() && !jobCountOverridesMakeflags())) { return {}; } return {"-j" + QString::number(m_jobCountAspect())}; } QString MakeStep::userArguments() const { return m_userArgumentsAspect(); } void MakeStep::setUserArguments(const QString &args) { m_userArgumentsAspect.setValue(args); } QStringList MakeStep::displayArguments() const { return {}; } FilePath MakeStep::makeCommand() const { return m_makeCommandAspect(); } FilePath MakeStep::makeExecutable() const { const FilePath cmd = makeCommand(); return cmd.isEmpty() ? defaultMakeCommand() : cmd; } CommandLine MakeStep::effectiveMakeCommand(MakeCommandType type) const { CommandLine cmd(makeExecutable()); if (type == Display) cmd.addArgs(displayArguments()); cmd.addArgs(userArguments(), CommandLine::Raw); cmd.addArgs(jobArguments()); cmd.addArgs(m_buildTargetsAspect()); return cmd; } QWidget *MakeStep::createConfigWidget() { Layouting::Form builder; builder.addRow({m_makeCommandAspect}); builder.addRow({m_userArgumentsAspect}); builder.addRow({m_jobCountAspect, m_overrideMakeflagsAspect, m_nonOverrideWarning}); if (m_disablingForSubDirsSupported) builder.addRow({m_disabledForSubdirsAspect}); builder.addRow({m_buildTargetsAspect}); if (m_runAsAspect.isVisible()) builder.addRow({m_runAsAspect}); builder.setNoMargins(); auto widget = builder.emerge(); setSummaryUpdater([this] { const CommandLine make = effectiveMakeCommand(MakeStep::Display); if (make.executable().isEmpty()) return Tr::tr("Make: %1").arg(MakeStep::msgNoMakeCommand()); if (!buildConfiguration()) return Tr::tr("Make: No build configuration."); ProcessParameters param; param.setMacroExpander(macroExpander()); param.setWorkingDirectory(buildDirectory()); param.setCommandLine(make); param.setEnvironment(buildEnvironment()); if (param.commandMissing()) { return Tr::tr("Make: %1 not found in the environment.") .arg(param.command().executable().toUserOutput()); // Override display text } return param.summaryInWorkdir(displayName()); }); auto updateDetails = [this] { const bool jobCountVisible = isJobCountSupported(); m_jobCountAspect.setVisible(jobCountVisible); m_overrideMakeflagsAspect.setVisible(jobCountVisible); const bool jobCountEnabled = !userArgsContainsJobCount(); m_jobCountAspect.setEnabled(jobCountEnabled); m_overrideMakeflagsAspect.setEnabled(jobCountEnabled); QString warningText; InfoLabel::InfoType iconType = InfoLabel::Information; if (makeflagsJobCountMismatch()) { if (m_overrideMakeflagsAspect.value()) { warningText = Tr::tr("Overriding MAKEFLAGS environment variable."); } else { warningText = Tr::tr("MAKEFLAGS specifies a conflicting job count."); iconType = InfoLabel::Warning; } } else { warningText = Tr::tr("No conflict with MAKEFLAGS environment variable."); } m_nonOverrideWarning.setText(QString::fromLatin1("

%1

") .arg(warningText)); m_nonOverrideWarning.setIconType(iconType); }; updateDetails(); connect(&m_makeCommandAspect, &StringAspect::changed, widget, updateDetails); connect(&m_userArgumentsAspect, &StringAspect::changed, widget, updateDetails); connect(&m_jobCountAspect, &IntegerAspect::changed, widget, updateDetails); connect(&m_overrideMakeflagsAspect, &BoolAspect::changed, widget, updateDetails); connect(&m_buildTargetsAspect, &BaseAspect::changed, widget, updateDetails); globalProjectExplorerSettings().useJom.addOnChanged(widget, updateDetails); connect(buildConfiguration(), &BuildConfiguration::kitChanged, widget, updateDetails); connect(buildConfiguration(), &BuildConfiguration::environmentChanged, widget, updateDetails); connect(buildConfiguration(), &BuildConfiguration::buildDirectoryChanged, widget, updateDetails); connect(buildSystem(), &BuildSystem::parsingFinished, widget, updateDetails); return widget; } QStringList MakeStep::availableTargets() const { return m_buildTargetsAspect.allValues(); } } // namespace ProjectExplorer