2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2013-04-25 16:02:17 +02:00
|
|
|
|
|
|
|
|
#include "iosdeploystep.h"
|
2020-02-20 13:21:52 +01:00
|
|
|
|
2013-04-25 16:02:17 +02:00
|
|
|
#include "iosconstants.h"
|
2020-02-20 13:21:52 +01:00
|
|
|
#include "iosdevice.h"
|
2013-04-25 16:02:17 +02:00
|
|
|
#include "iosrunconfiguration.h"
|
2020-02-20 13:21:52 +01:00
|
|
|
#include "iossimulator.h"
|
2013-04-25 16:02:17 +02:00
|
|
|
#include "iostoolhandler.h"
|
2022-12-20 13:39:23 +01:00
|
|
|
#include "iostr.h"
|
2013-04-25 16:02:17 +02:00
|
|
|
|
|
|
|
|
#include <projectexplorer/buildconfiguration.h>
|
|
|
|
|
#include <projectexplorer/projectexplorerconstants.h>
|
|
|
|
|
#include <projectexplorer/target.h>
|
2013-11-04 22:45:52 +01:00
|
|
|
#include <projectexplorer/taskhub.h>
|
2014-05-13 16:09:11 +02:00
|
|
|
#include <projectexplorer/kitmanager.h>
|
|
|
|
|
#include <projectexplorer/kitinformation.h>
|
|
|
|
|
#include <projectexplorer/devicesupport/devicemanager.h>
|
2013-04-25 16:02:17 +02:00
|
|
|
|
2023-07-14 11:37:50 +02:00
|
|
|
#include <solutions/tasking/tasktree.h>
|
|
|
|
|
|
2017-01-19 16:44:22 +01:00
|
|
|
#include <utils/temporaryfile.h>
|
|
|
|
|
|
2014-05-08 13:43:25 +02:00
|
|
|
#include <QFile>
|
|
|
|
|
#include <QSettings>
|
2013-04-25 16:02:17 +02:00
|
|
|
|
|
|
|
|
using namespace ProjectExplorer;
|
2023-07-14 11:37:50 +02:00
|
|
|
using namespace Tasking;
|
2019-05-15 15:49:19 +02:00
|
|
|
using namespace Utils;
|
2013-04-25 16:02:17 +02:00
|
|
|
|
2022-12-20 13:39:23 +01:00
|
|
|
namespace Ios::Internal {
|
2013-04-25 16:02:17 +02:00
|
|
|
|
2023-07-14 11:37:50 +02:00
|
|
|
class IosTransfer : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
void setDeviceType(const IosDeviceType &deviceType) { m_deviceType = deviceType; }
|
|
|
|
|
void setBundlePath(const FilePath &bundlePath) { m_bundlePath = bundlePath; }
|
|
|
|
|
void setExpectSuccess(bool success) { m_expectSuccess = success; }
|
|
|
|
|
void start()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_deviceType, emit done(false); return);
|
|
|
|
|
QTC_ASSERT(!m_toolHandler, return);
|
|
|
|
|
|
|
|
|
|
m_toolHandler.reset(new IosToolHandler(*m_deviceType));
|
|
|
|
|
connect(m_toolHandler.get(), &IosToolHandler::isTransferringApp, this,
|
|
|
|
|
[this](IosToolHandler *, const FilePath &, const QString &,
|
|
|
|
|
int progress, int maxProgress, const QString &info) {
|
|
|
|
|
emit progressValueChanged(progress * 100 / maxProgress, info);
|
|
|
|
|
});
|
|
|
|
|
connect(m_toolHandler.get(), &IosToolHandler::errorMsg, this,
|
|
|
|
|
[this](IosToolHandler *, const QString &message) {
|
|
|
|
|
if (message.contains(QLatin1String("AMDeviceInstallApplication returned -402653103")))
|
|
|
|
|
TaskHub::addTask(DeploymentTask(Task::Warning, Tr::tr("The Info.plist might be incorrect.")));
|
|
|
|
|
emit errorMessage(message);
|
|
|
|
|
});
|
|
|
|
|
connect(m_toolHandler.get(), &IosToolHandler::didTransferApp, this,
|
|
|
|
|
[this](IosToolHandler *, const FilePath &, const QString &,
|
|
|
|
|
IosToolHandler::OpStatus status) {
|
|
|
|
|
disconnect(m_toolHandler.get(), nullptr, this, nullptr);
|
|
|
|
|
m_toolHandler.release()->deleteLater();
|
|
|
|
|
if (status != IosToolHandler::Success && m_expectSuccess) {
|
|
|
|
|
TaskHub::addTask(DeploymentTask(Task::Error, Tr::tr("Deployment failed. "
|
|
|
|
|
"The settings in the Devices window of Xcode might be incorrect.")));
|
|
|
|
|
}
|
|
|
|
|
emit done(status == IosToolHandler::Success);
|
|
|
|
|
});
|
|
|
|
|
connect(m_toolHandler.get(), &IosToolHandler::finished, this, [this] {
|
|
|
|
|
disconnect(m_toolHandler.get(), nullptr, this, nullptr);
|
|
|
|
|
m_toolHandler.release()->deleteLater();
|
|
|
|
|
TaskHub::addTask(DeploymentTask(Task::Error, Tr::tr("Deployment failed.")));
|
|
|
|
|
emit done(false);
|
|
|
|
|
});
|
|
|
|
|
m_toolHandler->requestTransferApp(m_bundlePath, m_deviceType->identifier);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
signals:
|
|
|
|
|
void done(bool success);
|
|
|
|
|
void progressValueChanged(int progress, const QString &info); // progress in %
|
|
|
|
|
void errorMessage(const QString &message);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
std::optional<IosDeviceType> m_deviceType;
|
|
|
|
|
FilePath m_bundlePath;
|
|
|
|
|
bool m_expectSuccess = true;
|
|
|
|
|
std::unique_ptr<IosToolHandler> m_toolHandler;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class IosTransferTaskAdapter : public TaskAdapter<IosTransfer>
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
IosTransferTaskAdapter() { connect(task(), &IosTransfer::done, this, &TaskInterface::done); }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void start() final { task()->start(); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // Ios::Internal
|
|
|
|
|
|
|
|
|
|
TASKING_DECLARE_TASK(IosTransferTask, Ios::Internal::IosTransferTaskAdapter);
|
|
|
|
|
|
|
|
|
|
namespace Ios::Internal {
|
|
|
|
|
|
2020-02-20 13:21:52 +01:00
|
|
|
class IosDeployStep final : public BuildStep
|
|
|
|
|
{
|
|
|
|
|
public:
|
2020-06-26 13:59:38 +02:00
|
|
|
IosDeployStep(BuildStepList *bc, Utils::Id id);
|
2020-02-20 13:21:52 +01:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void cleanup();
|
|
|
|
|
|
2023-07-14 12:32:06 +02:00
|
|
|
Tasking::GroupItem runRecipe() final;
|
2020-02-20 13:21:52 +01:00
|
|
|
|
|
|
|
|
void updateDisplayNames();
|
|
|
|
|
|
|
|
|
|
bool init() final;
|
2020-10-02 17:53:39 +02:00
|
|
|
QWidget *createConfigWidget() final;
|
2020-02-20 13:21:52 +01:00
|
|
|
IosDevice::ConstPtr iosdevice() const;
|
|
|
|
|
IosSimulator::ConstPtr iossimulator() const;
|
|
|
|
|
|
|
|
|
|
QString deviceId() const;
|
2023-07-14 12:57:23 +02:00
|
|
|
bool checkProvisioningProfile();
|
2020-02-20 13:21:52 +01:00
|
|
|
|
|
|
|
|
IDevice::ConstPtr m_device;
|
|
|
|
|
FilePath m_bundlePath;
|
|
|
|
|
IosDeviceType m_deviceType;
|
|
|
|
|
};
|
|
|
|
|
|
2020-06-26 13:59:38 +02:00
|
|
|
IosDeployStep::IosDeployStep(BuildStepList *parent, Utils::Id id)
|
2019-12-20 17:05:30 +01:00
|
|
|
: BuildStep(parent, id)
|
2013-04-25 16:02:17 +02:00
|
|
|
{
|
2018-10-22 17:56:04 +02:00
|
|
|
setImmutable(true);
|
2014-05-13 16:09:11 +02:00
|
|
|
updateDisplayNames();
|
2016-06-29 19:35:23 +03:00
|
|
|
connect(DeviceManager::instance(), &DeviceManager::updated,
|
|
|
|
|
this, &IosDeployStep::updateDisplayNames);
|
|
|
|
|
connect(target(), &Target::kitChanged,
|
|
|
|
|
this, &IosDeployStep::updateDisplayNames);
|
2014-05-13 16:09:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IosDeployStep::updateDisplayNames()
|
|
|
|
|
{
|
2020-09-07 15:56:18 +02:00
|
|
|
IDevice::ConstPtr dev = DeviceKitAspect::device(kit());
|
2014-05-13 16:09:11 +02:00
|
|
|
const QString devName = dev.isNull() ? IosDevice::name() : dev->displayName();
|
2022-12-20 13:39:23 +01:00
|
|
|
setDisplayName(Tr::tr("Deploy to %1").arg(devName));
|
2013-04-25 16:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-10 15:31:44 +01:00
|
|
|
bool IosDeployStep::init()
|
2013-04-25 16:02:17 +02:00
|
|
|
{
|
2020-09-07 15:56:18 +02:00
|
|
|
m_device = DeviceKitAspect::device(kit());
|
2018-11-12 19:55:59 +01:00
|
|
|
auto runConfig = qobject_cast<const IosRunConfiguration *>(
|
|
|
|
|
this->target()->activeRunConfiguration());
|
2014-05-17 22:53:00 +02:00
|
|
|
QTC_ASSERT(runConfig, return false);
|
2019-05-15 15:49:19 +02:00
|
|
|
m_bundlePath = runConfig->bundleDirectory();
|
2016-09-26 12:40:09 +02:00
|
|
|
|
|
|
|
|
if (iosdevice()) {
|
|
|
|
|
m_deviceType = IosDeviceType(IosDeviceType::IosDevice, deviceId());
|
|
|
|
|
} else if (iossimulator()) {
|
|
|
|
|
m_deviceType = runConfig->deviceType();
|
|
|
|
|
} else {
|
2022-12-20 13:39:23 +01:00
|
|
|
emit addOutput(Tr::tr("Error: no device available, deploy failed."),
|
2020-02-20 13:21:52 +01:00
|
|
|
OutputFormat::ErrorMessage);
|
2013-10-07 16:07:16 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2013-04-25 16:02:17 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-14 12:32:06 +02:00
|
|
|
GroupItem IosDeployStep::runRecipe()
|
|
|
|
|
{
|
|
|
|
|
const auto onSetup = [this](IosTransfer &transfer) {
|
|
|
|
|
if (m_device.isNull()) {
|
|
|
|
|
TaskHub::addTask(
|
|
|
|
|
DeploymentTask(Task::Error, Tr::tr("Deployment failed. No iOS device found.")));
|
|
|
|
|
return SetupResult::StopWithError;
|
|
|
|
|
}
|
|
|
|
|
transfer.setDeviceType(m_deviceType);
|
|
|
|
|
transfer.setBundlePath(m_bundlePath);
|
|
|
|
|
transfer.setExpectSuccess(checkProvisioningProfile());
|
|
|
|
|
emit progress(0, Tr::tr("Transferring application"));
|
|
|
|
|
connect(&transfer, &IosTransfer::progressValueChanged, this, &IosDeployStep::progress);
|
|
|
|
|
connect(&transfer, &IosTransfer::errorMessage, this, [this](const QString &message) {
|
|
|
|
|
emit addOutput(message, OutputFormat::ErrorMessage);
|
|
|
|
|
});
|
|
|
|
|
return SetupResult::Continue;
|
|
|
|
|
};
|
|
|
|
|
return IosTransferTask(onSetup);
|
2013-10-07 16:07:16 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-02 17:53:39 +02:00
|
|
|
QWidget *IosDeployStep::createConfigWidget()
|
2013-04-25 16:02:17 +02:00
|
|
|
{
|
2020-10-02 17:53:39 +02:00
|
|
|
auto widget = new QWidget;
|
2019-04-23 18:18:03 +02:00
|
|
|
|
|
|
|
|
widget->setObjectName("IosDeployStepWidget");
|
|
|
|
|
|
|
|
|
|
connect(this, &ProjectConfiguration::displayNameChanged,
|
2020-09-14 12:37:32 +02:00
|
|
|
this, &BuildStep::updateSummary);
|
2019-04-23 18:18:03 +02:00
|
|
|
|
|
|
|
|
return widget;
|
2013-04-25 16:02:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString IosDeployStep::deviceId() const
|
|
|
|
|
{
|
|
|
|
|
if (iosdevice().isNull())
|
|
|
|
|
return QString();
|
|
|
|
|
return iosdevice()->uniqueDeviceID();
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-14 12:57:23 +02:00
|
|
|
bool IosDeployStep::checkProvisioningProfile()
|
2014-05-08 13:43:25 +02:00
|
|
|
{
|
|
|
|
|
IosDevice::ConstPtr device = iosdevice();
|
|
|
|
|
if (device.isNull())
|
2023-07-14 12:57:23 +02:00
|
|
|
return true;
|
2014-05-08 13:43:25 +02:00
|
|
|
|
2019-05-28 13:49:26 +02:00
|
|
|
const FilePath provisioningFilePath = m_bundlePath.pathAppended("embedded.mobileprovision");
|
2014-05-08 13:43:25 +02:00
|
|
|
// the file is a signed plist stored in DER format
|
|
|
|
|
// we simply search for start and end of the plist instead of decoding the DER payload
|
2014-10-24 13:15:54 +02:00
|
|
|
if (!provisioningFilePath.exists())
|
2023-07-14 12:57:23 +02:00
|
|
|
return true;
|
|
|
|
|
|
2014-05-08 13:43:25 +02:00
|
|
|
QFile provisionFile(provisioningFilePath.toString());
|
|
|
|
|
if (!provisionFile.open(QIODevice::ReadOnly))
|
2023-07-14 12:57:23 +02:00
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
const QByteArray provisionData = provisionFile.readAll();
|
|
|
|
|
const int start = provisionData.indexOf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
2014-05-08 13:43:25 +02:00
|
|
|
int end = provisionData.indexOf("</plist>");
|
|
|
|
|
if (start == -1 || end == -1)
|
2023-07-14 12:57:23 +02:00
|
|
|
return true;
|
2014-05-08 13:43:25 +02:00
|
|
|
|
2023-07-14 12:57:23 +02:00
|
|
|
end += 8;
|
2020-02-20 13:21:52 +01:00
|
|
|
TemporaryFile f("iosdeploy");
|
2014-05-08 13:43:25 +02:00
|
|
|
if (!f.open())
|
2023-07-14 12:57:23 +02:00
|
|
|
return true;
|
|
|
|
|
|
2014-05-08 13:43:25 +02:00
|
|
|
f.write(provisionData.mid(start, end - start));
|
|
|
|
|
f.flush();
|
2023-07-14 12:57:23 +02:00
|
|
|
const QSettings provisionPlist(f.fileName(), QSettings::NativeFormat);
|
2014-05-08 13:43:25 +02:00
|
|
|
if (!provisionPlist.contains(QLatin1String("ProvisionedDevices")))
|
2023-07-14 12:57:23 +02:00
|
|
|
return true;
|
|
|
|
|
|
2020-02-18 17:50:44 +01:00
|
|
|
const QStringList deviceIds = provisionPlist.value("ProvisionedDevices").toStringList();
|
|
|
|
|
const QString targetId = device->uniqueDeviceID();
|
|
|
|
|
for (const QString &deviceId : deviceIds) {
|
2014-05-08 13:43:25 +02:00
|
|
|
if (deviceId == targetId)
|
2023-07-14 12:57:23 +02:00
|
|
|
return true;
|
2014-05-08 13:43:25 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-13 11:19:23 +02:00
|
|
|
const QString provisioningProfile = provisionPlist.value(QLatin1String("Name")).toString();
|
|
|
|
|
const QString provisioningUid = provisionPlist.value(QLatin1String("UUID")).toString();
|
2023-07-14 12:57:23 +02:00
|
|
|
const CompileTask task(Task::Warning,
|
2022-12-20 13:39:23 +01:00
|
|
|
Tr::tr("The provisioning profile \"%1\" (%2) used to sign the application "
|
2023-07-14 12:57:23 +02:00
|
|
|
"does not cover the device %3 (%4). Deployment to it will fail.")
|
|
|
|
|
.arg(provisioningProfile, provisioningUid, device->displayName(), targetId));
|
2014-05-08 13:43:25 +02:00
|
|
|
emit addTask(task);
|
2023-07-14 12:57:23 +02:00
|
|
|
return false;
|
2014-05-08 13:43:25 +02:00
|
|
|
}
|
|
|
|
|
|
2013-04-25 16:02:17 +02:00
|
|
|
IosDevice::ConstPtr IosDeployStep::iosdevice() const
|
|
|
|
|
{
|
|
|
|
|
return m_device.dynamicCast<const IosDevice>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IosSimulator::ConstPtr IosDeployStep::iossimulator() const
|
|
|
|
|
{
|
|
|
|
|
return m_device.dynamicCast<const IosSimulator>();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-20 13:21:52 +01:00
|
|
|
// IosDeployStepFactory
|
|
|
|
|
|
|
|
|
|
IosDeployStepFactory::IosDeployStepFactory()
|
|
|
|
|
{
|
2020-08-18 11:33:37 +02:00
|
|
|
registerStep<IosDeployStep>(Constants::IOS_DEPLOY_STEP_ID);
|
2022-12-20 13:39:23 +01:00
|
|
|
setDisplayName(Tr::tr("Deploy to iOS device"));
|
2020-02-20 13:21:52 +01:00
|
|
|
setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY);
|
|
|
|
|
setSupportedDeviceTypes({Constants::IOS_DEVICE_TYPE, Constants::IOS_SIMULATOR_TYPE});
|
|
|
|
|
setRepeatable(false);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 13:39:23 +01:00
|
|
|
} // Ios::Internal
|
2023-07-14 11:37:50 +02:00
|
|
|
|
|
|
|
|
#include "iosdeploystep.moc"
|