// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "makeinstallstep.h" #include "remotelinux_constants.h" #include "remotelinuxtr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace RemoteLinux { const char MakeAspectId[] = "RemoteLinux.MakeInstall.Make"; const char InstallRootAspectId[] = "RemoteLinux.MakeInstall.InstallRoot"; const char CleanInstallRootAspectId[] = "RemoteLinux.MakeInstall.CleanInstallRoot"; const char FullCommandLineAspectId[] = "RemoteLinux.MakeInstall.FullCommandLine"; const char CustomCommandLineAspectId[] = "RemoteLinux.MakeInstall.CustomCommandLine"; MakeInstallStep::MakeInstallStep(BuildStepList *parent, Id id) : MakeStep(parent, id) { makeCommandAspect()->setVisible(false); buildTargetsAspect()->setVisible(false); userArgumentsAspect()->setVisible(false); overrideMakeflagsAspect()->setVisible(false); nonOverrideWarning()->setVisible(false); jobCountAspect()->setVisible(false); disabledForSubdirsAspect()->setVisible(false); // FIXME: Hack, Part#1: If the build device is not local, start with a temp dir // inside the build dir. On Docker that's typically shared with the host. const IDevice::ConstPtr device = BuildDeviceKitAspect::device(target()->kit()); const bool hack = device && device->type() != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE; FilePath rootPath; if (hack) { rootPath = buildDirectory().pathAppended(".tmp-root"); } else { QTemporaryDir tmpDir; rootPath = FilePath::fromString(tmpDir.path()); } const auto makeAspect = addAspect(parent->target(), ExecutableAspect::BuildDevice); makeAspect->setId(MakeAspectId); makeAspect->setSettingsKey(MakeAspectId); makeAspect->setDisplayStyle(StringAspect::PathChooserDisplay); makeAspect->setLabelText(Tr::tr("Command:")); connect(makeAspect, &ExecutableAspect::changed, this, &MakeInstallStep::updateCommandFromAspect); const auto installRootAspect = addAspect(); installRootAspect->setId(InstallRootAspectId); installRootAspect->setSettingsKey(InstallRootAspectId); installRootAspect->setDisplayStyle(StringAspect::PathChooserDisplay); installRootAspect->setExpectedKind(PathChooser::Directory); installRootAspect->setLabelText(Tr::tr("Install root:")); installRootAspect->setFilePath(rootPath); connect(installRootAspect, &StringAspect::changed, this, &MakeInstallStep::updateArgsFromAspect); const auto cleanInstallRootAspect = addAspect(); cleanInstallRootAspect->setId(CleanInstallRootAspectId); cleanInstallRootAspect->setSettingsKey(CleanInstallRootAspectId); cleanInstallRootAspect->setLabel(Tr::tr("Clean install root first:"), BoolAspect::LabelPlacement::InExtraLabel); cleanInstallRootAspect->setValue(true); const auto commandLineAspect = addAspect(); commandLineAspect->setId(FullCommandLineAspectId); commandLineAspect->setDisplayStyle(StringAspect::LabelDisplay); commandLineAspect->setLabelText(Tr::tr("Full command line:")); const auto customCommandLineAspect = addAspect(); customCommandLineAspect->setId(CustomCommandLineAspectId); customCommandLineAspect->setSettingsKey(CustomCommandLineAspectId); customCommandLineAspect->setDisplayStyle(StringAspect::LineEditDisplay); customCommandLineAspect->setLabelText(Tr::tr("Custom command line:")); customCommandLineAspect->makeCheckable(StringAspect::CheckBoxPlacement::Top, Tr::tr("Use custom command line instead:"), "RemoteLinux.MakeInstall.EnableCustomCommandLine"); const auto updateCommand = [this] { updateCommandFromAspect(); updateArgsFromAspect(); updateFromCustomCommandLineAspect(); }; connect(customCommandLineAspect, &StringAspect::checkedChanged, this, updateCommand); connect(customCommandLineAspect, &StringAspect::changed, this, &MakeInstallStep::updateFromCustomCommandLineAspect); connect(target(), &Target::buildSystemUpdated, this, updateCommand); const MakeInstallCommand cmd = buildSystem()->makeInstallCommand(rootPath); QTC_ASSERT(!cmd.command.isEmpty(), return); makeAspect->setExecutable(cmd.command.executable()); connect(this, &BuildStep::addOutput, this, [this](const QString &string, OutputFormat format) { // When using Makefiles: "No rule to make target 'install'" // When using ninja: "ninja: error: unknown target 'install'" if (format == OutputFormat::Stderr && string.contains("target 'install'")) m_noInstallTarget = true; }); } QWidget *MakeInstallStep::createConfigWidget() { // Note: this intentionally skips the MakeStep::createConfigWidget() level. return BuildStep::createConfigWidget(); } bool MakeInstallStep::init() { if (!MakeStep::init()) return false; const FilePath rootDir = makeCommand().withNewPath(installRoot().path()); // FIXME: Needed? if (rootDir.isEmpty()) { emit addTask(BuildSystemTask(Task::Error, Tr::tr("You must provide an install root."))); return false; } if (cleanInstallRoot() && !rootDir.removeRecursively()) { emit addTask(BuildSystemTask(Task::Error, Tr::tr("The install root \"%1\" could not be cleaned.") .arg(rootDir.displayName()))); return false; } if (!rootDir.exists() && !rootDir.createDir()) { emit addTask(BuildSystemTask(Task::Error, Tr::tr("The install root \"%1\" could not be created.") .arg(rootDir.displayName()))); return false; } if (this == deployConfiguration()->stepList()->steps().last()) { emit addTask(BuildSystemTask(Task::Warning, Tr::tr("The \"make install\" step should probably not be " "last in the list of deploy steps. " "Consider moving it up."))); } const MakeInstallCommand cmd = buildSystem()->makeInstallCommand(rootDir); if (cmd.environment.hasChanges()) { Environment env = processParameters()->environment(); cmd.environment.forEachEntry([&](const QString &key, const QString &value, bool enabled) { if (enabled) env.set(key, cmd.environment.expandVariables(value)); }); processParameters()->setEnvironment(env); } m_noInstallTarget = false; const auto buildStep = buildConfiguration()->buildSteps()->firstOfType(); m_isCmakeProject = buildStep && buildStep->processParameters()->command().executable().toString() .contains("cmake"); return true; } void MakeInstallStep::finish(ProcessResult result) { if (isSuccess(result)) { const FilePath rootDir = makeCommand().withNewPath(installRoot().path()); // FIXME: Needed? m_deploymentData = DeploymentData(); m_deploymentData.setLocalInstallRoot(rootDir); const int startPos = rootDir.path().length(); const auto appFileNames = transform>(buildSystem()->applicationTargets(), [](const BuildTargetInfo &appTarget) { return appTarget.targetFilePath.fileName(); }); auto handleFile = [this, &appFileNames, startPos](const FilePath &filePath) { const DeployableFile::Type type = appFileNames.contains(filePath.fileName()) ? DeployableFile::TypeExecutable : DeployableFile::TypeNormal; const QString targetDir = filePath.parentDir().path().mid(startPos); m_deploymentData.addFile(filePath, targetDir, type); return IterationPolicy::Continue; }; rootDir.iterateDirectory(handleFile, {{}, QDir::Files | QDir::Hidden, QDirIterator::Subdirectories}); buildSystem()->setDeploymentData(m_deploymentData); } else if (m_noInstallTarget && m_isCmakeProject) { emit addTask(DeploymentTask(Task::Warning, Tr::tr("You need to add an install statement " "to your CMakeLists.txt file for deployment to work."))); } MakeStep::finish(result); } FilePath MakeInstallStep::installRoot() const { return static_cast(aspect(InstallRootAspectId))->filePath(); } bool MakeInstallStep::cleanInstallRoot() const { return static_cast(aspect(CleanInstallRootAspectId))->value(); } void MakeInstallStep::updateCommandFromAspect() { if (customCommandLineAspect()->isChecked()) return; setMakeCommand(aspect()->executable()); updateFullCommandLine(); } void MakeInstallStep::updateArgsFromAspect() { if (customCommandLineAspect()->isChecked()) return; const CommandLine cmd = buildSystem()->makeInstallCommand(installRoot()).command; setUserArguments(cmd.arguments()); updateFullCommandLine(); } void MakeInstallStep::updateFullCommandLine() { CommandLine cmd{makeExecutable(), userArguments(), CommandLine::Raw}; static_cast(aspect(FullCommandLineAspectId))->setValue(cmd.toUserOutput()); } void MakeInstallStep::updateFromCustomCommandLineAspect() { const StringAspect * const aspect = customCommandLineAspect(); if (!aspect->isChecked()) return; const QStringList tokens = ProcessArgs::splitArgs(aspect->value(), HostOsInfo::hostOs()); setMakeCommand(tokens.isEmpty() ? FilePath() : FilePath::fromString(tokens.first())); setUserArguments(ProcessArgs::joinArgs(tokens.mid(1))); } StringAspect *MakeInstallStep::customCommandLineAspect() const { return static_cast(aspect(CustomCommandLineAspectId)); } bool MakeInstallStep::fromMap(const QVariantMap &map) { if (!MakeStep::fromMap(map)) return false; updateCommandFromAspect(); updateArgsFromAspect(); updateFromCustomCommandLineAspect(); return true; } // Factory MakeInstallStepFactory::MakeInstallStepFactory() { registerStep(Constants::MakeInstallStepId); setDisplayName(Tr::tr("Install into temporary host directory")); } } // RemoteLinux