iOS: Add API's to get provisioning data

Change-Id: I927b2dbaa9e6c175d90b1407418570bbd2a3d96e
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Vikas Pachdha
2017-03-03 16:20:53 +01:00
parent 6f0a31166d
commit 7c95aedfd3
2 changed files with 281 additions and 6 deletions

View File

@@ -32,6 +32,7 @@
#include <coreplugin/icore.h>
#include <utils/algorithm.h>
#include <utils/synchronousprocess.h>
#include <utils/qtcassert.h>
#include <utils/synchronousprocess.h>
#include <projectexplorer/kitmanager.h>
@@ -49,12 +50,15 @@
#include <qtsupport/qtversionmanager.h>
#include <qtsupport/qtversionfactory.h>
#include <QDir>
#include <QDomDocument>
#include <QFileInfo>
#include <QFileSystemWatcher>
#include <QHash>
#include <QList>
#include <QLoggingCategory>
#include <QProcess>
#include <QSettings>
#include <QStringList>
#include <QTimer>
using namespace ProjectExplorer;
@@ -74,6 +78,21 @@ namespace Internal {
const QLatin1String SettingsGroup("IosConfigurations");
const QLatin1String ignoreAllDevicesKey("IgnoreAllDevices");
const char provisioningTeamsTag[] = "IDEProvisioningTeams";
const char freeTeamTag[] = "isFreeProvisioningTeam";
const char emailTag[] = "eMail";
const char teamNameTag[] = "teamName";
const char teamIdTag[] = "teamID";
const char udidTag[] = "UUID";
const char profileNameTag[] = "Name";
const char appIdTag[] = "AppIDName";
const char expirationDateTag[] = "ExpirationDate";
const char profileTeamIdTag[] = "TeamIdentifier";
static const QString xcodePlistPath = QDir::homePath() + "/Library/Preferences/com.apple.dt.Xcode.plist";
static const QString provisioningProfileDirPath = QDir::homePath() + "/Library/MobileDevice/Provisioning Profiles";
static Core::Id deviceId(const Platform &platform)
{
if (platform.name.startsWith(QLatin1String("iphoneos-")))
@@ -246,6 +265,20 @@ static QVersionNumber findXcodeVersion()
return QVersionNumber();
}
static QByteArray decodeProvisioningProfile(const QString &path)
{
QTC_ASSERT(!path.isEmpty(), return QByteArray());
Utils::SynchronousProcess p;
p.setTimeoutS(3);
// path is assumed to be valid file path to .mobileprovision
const QStringList args = {"smime", "-inform", "der", "-verify", "-in", path};
Utils::SynchronousProcessResponse res = p.runBlocking("openssl", args);
if (res.result != Utils::SynchronousProcessResponse::Finished)
qCDebug(iosCommonLog) << "Reading signed provisioning file failed" << path;
return res.stdOut().toLatin1();
}
void IosConfigurations::updateAutomaticKitList()
{
const QList<Platform> platforms = handledPlatforms();
@@ -320,9 +353,9 @@ void IosConfigurations::updateAutomaticKitList()
KitManager::deregisterKit(kit);
}
static IosConfigurations *m_instance = 0;
static IosConfigurations *m_instance = nullptr;
QObject *IosConfigurations::instance()
IosConfigurations *IosConfigurations::instance()
{
return m_instance;
}
@@ -409,6 +442,137 @@ void IosConfigurations::setDeveloperPath(const FileName &devPath)
}
}
void IosConfigurations::initializeProvisioningData()
{
// Initialize provisioning data only on demand. i.e. when first call to a provisioing data API
// is made.
static bool initialized = false;
if (initialized)
return;
initialized = true;
m_instance->loadProvisioningData(false);
// Watch the provisioing profiles folder and xcode plist for changes and
// update the content accordingly.
m_provisioningDataWatcher = new QFileSystemWatcher(this);
m_provisioningDataWatcher->addPath(xcodePlistPath);
m_provisioningDataWatcher->addPath(provisioningProfileDirPath);
connect(m_provisioningDataWatcher, &QFileSystemWatcher::directoryChanged,
std::bind(&IosConfigurations::loadProvisioningData, this, true));
connect(m_provisioningDataWatcher, &QFileSystemWatcher::fileChanged,
std::bind(&IosConfigurations::loadProvisioningData, this, true));
}
void IosConfigurations::loadProvisioningData(bool notify)
{
m_developerTeams.clear();
m_provisioningProfiles.clear();
// Populate Team id's
const QSettings xcodeSettings(xcodePlistPath, QSettings::NativeFormat);
const QVariantMap teamMap = xcodeSettings.value(provisioningTeamsTag).toMap();
QList<QVariantMap> teams;
QMapIterator<QString, QVariant> accountiterator(teamMap);
while (accountiterator.hasNext()) {
accountiterator.next();
QVariantMap teamInfo = accountiterator.value().toMap();
int provisioningTeamIsFree = teamInfo.value(freeTeamTag).toBool() ? 1 : 0;
teamInfo[freeTeamTag] = provisioningTeamIsFree;
teamInfo[emailTag] = accountiterator.key();
teams.append(teamInfo);
}
// Sort team id's to move the free provisioning teams at last of the list.
Utils::sort(teams, [](const QVariantMap &teamInfo1, const QVariantMap &teamInfo2) {
return teamInfo1.value(freeTeamTag).toInt() < teamInfo2.value(freeTeamTag).toInt();
});
foreach (auto teamInfo, teams) {
auto team = std::shared_ptr<DevelopmentTeam>::make_shared();
team->m_name = teamInfo.value(teamNameTag).toString();
team->m_email = teamInfo.value(emailTag).toString();
team->m_identifier = teamInfo.value(teamIdTag).toString();
team->m_freeTeam = teamInfo.value(freeTeamTag).toInt() == 1;
m_developerTeams.append(team);
}
const QDir provisioningProflesDir(provisioningProfileDirPath);
foreach (QFileInfo fileInfo, provisioningProflesDir.entryInfoList({"*.mobileprovision"}, QDir::NoDotAndDotDot | QDir::Files)) {
QDomDocument provisioningDoc;
auto profile = ProvisioningProfilePtr::make_shared();;
QString teamID;
if (provisioningDoc.setContent(decodeProvisioningProfile(fileInfo.absoluteFilePath()))) {
QDomNodeList nodes = provisioningDoc.elementsByTagName("key");
for (int i = 0;i<nodes.count(); ++i) {
QDomElement e = nodes.at(i).toElement();
if (e.text().compare(udidTag) == 0)
profile->m_identifier = e.nextSiblingElement().text();
if (e.text().compare(profileNameTag) == 0)
profile->m_name = e.nextSiblingElement().text();
if (e.text().compare(appIdTag) == 0)
profile->m_appID = e.nextSiblingElement().text();
if (e.text().compare(expirationDateTag) == 0)
profile->m_expirationDate = QDateTime::fromString(e.nextSiblingElement().text(),
Qt::ISODate).toUTC();
if (e.text().compare(profileTeamIdTag) == 0) {
teamID = e.nextSibling().firstChildElement().text();
auto team = developmentTeam(teamID);
if (team) {
profile->m_team = team;
team->m_profiles.append(profile);
}
}
}
} else {
qCDebug(iosCommonLog) << "Error reading provisoing profile" << fileInfo.absoluteFilePath();
}
if (profile->m_team)
m_provisioningProfiles.append(profile);
else
qCDebug(iosCommonLog) << "Skipping profile. No corresponding team found" << profile;
}
if (notify)
emit provisioningDataChanged();
}
const DevelopmentTeams &IosConfigurations::developmentTeams()
{
QTC_CHECK(m_instance);
m_instance->initializeProvisioningData();
return m_instance->m_developerTeams;
}
DevelopmentTeamPtr IosConfigurations::developmentTeam(const QString &teamID)
{
QTC_CHECK(m_instance);
m_instance->initializeProvisioningData();
return findOrDefault(m_instance->m_developerTeams,
Utils::equal(&DevelopmentTeam::identifier, teamID));
}
const ProvisioningProfiles &IosConfigurations::provisioningProfiles()
{
QTC_CHECK(m_instance);
m_instance->initializeProvisioningData();
return m_instance->m_provisioningProfiles;
}
ProvisioningProfilePtr IosConfigurations::provisioningProfile(const QString &profileID)
{
QTC_CHECK(m_instance);
m_instance->initializeProvisioningData();
return Utils::findOrDefault(m_instance->m_provisioningProfiles,
Utils::equal(&ProvisioningProfile::identifier, profileID));
}
static ClangToolChain *createToolChain(const Platform &platform, Core::Id l)
{
if (!l.isValid())
@@ -455,5 +619,52 @@ QList<ToolChain *> IosToolChainFactory::autoDetect(const QList<ToolChain *> &exi
return Utils::transform(toolChains, [](ClangToolChain *tc) -> ToolChain * { return tc; });
}
QString DevelopmentTeam::identifier() const
{
return m_identifier;
}
QString DevelopmentTeam::displayName() const
{
return QString("%1 - %2").arg(m_email).arg(m_name);
}
QString DevelopmentTeam::details() const
{
return tr("%1 - Free Provisioning Team : %2")
.arg(m_identifier).arg(m_freeTeam ? tr("Yes") : tr("No"));
}
QDebug &operator<<(QDebug &stream, DevelopmentTeamPtr team)
{
QTC_ASSERT(team, return stream);
stream << team->displayName() << team->identifier() << team->isFreeProfile();
foreach (auto profile, team->m_profiles)
stream << "Profile:" << profile;
return stream;
}
QString ProvisioningProfile::identifier() const
{
return m_identifier;
}
QString ProvisioningProfile::displayName() const
{
return m_name;
}
QString ProvisioningProfile::details() const
{
return tr("Team: %1\nApp ID: %2\nExpiration date: %3").arg(m_team->identifier()).arg(m_appID)
.arg(m_expirationDate.toLocalTime().toString(Qt::SystemLocaleShortDate));
}
QDebug &operator<<(QDebug &stream, std::shared_ptr<ProvisioningProfile> profile)
{
QTC_ASSERT(profile, return stream);
return stream << profile->displayName() << profile->identifier() << profile->details();
}
} // namespace Internal
} // namespace Ios

View File

@@ -29,6 +29,7 @@
#include <projectexplorer/toolchain.h>
#include <utils/fileutils.h>
#include <QDateTime>
#include <QObject>
#include <QString>
#include <QStringList>
@@ -36,11 +37,62 @@
QT_BEGIN_NAMESPACE
class QSettings;
class QFileSystemWatcher;
QT_END_NAMESPACE
namespace Ios {
namespace Internal {
class DevelopmentTeam;
class ProvisioningProfile
{
Q_DECLARE_TR_FUNCTIONS(ProvisioningProfile)
public:
ProvisioningProfile() {}
std::shared_ptr<DevelopmentTeam> developmentTeam() { return m_team; }
QString identifier() const;
QString displayName() const;
QString details() const;
const QDateTime &expirationDate() const { return m_expirationDate; }
private:
std::shared_ptr<DevelopmentTeam> m_team;
QString m_identifier;
QString m_name;
QString m_appID;
QDateTime m_expirationDate;
friend class IosConfigurations;
friend QDebug &operator<<(QDebug &stream, std::shared_ptr<ProvisioningProfile> profile);
};
using ProvisioningProfilePtr = std::shared_ptr<ProvisioningProfile>;
using ProvisioningProfiles = QList<ProvisioningProfilePtr>;
class DevelopmentTeam
{
Q_DECLARE_TR_FUNCTIONS(DevelopmentTeam)
public:
DevelopmentTeam() {}
QString identifier() const;
QString displayName() const;
QString details() const;
bool isFreeProfile() const { return m_freeTeam; }
bool hasProvisioningProfile() const { return !m_profiles.isEmpty(); }
private:
QString m_identifier;
QString m_name;
QString m_email;
bool m_freeTeam = false;
ProvisioningProfiles m_profiles;
friend class IosConfigurations;
friend QDebug &operator<<(QDebug &stream, std::shared_ptr<DevelopmentTeam> team);
};
using DevelopmentTeamPtr = std::shared_ptr<DevelopmentTeam>;
using DevelopmentTeams = QList<DevelopmentTeamPtr>;
class IosToolChainFactory : public ProjectExplorer::ToolChainFactory
{
Q_OBJECT
@@ -50,13 +102,12 @@ public:
QList<ProjectExplorer::ToolChain *> autoDetect(const QList<ProjectExplorer::ToolChain *> &existingToolChains) override;
};
class IosConfigurations : public QObject
{
Q_OBJECT
public:
static QObject *instance();
static IosConfigurations *instance();
static void initialize();
static bool ignoreAllDevices();
static void setIgnoreAllDevices(bool ignoreDevices);
@@ -64,6 +115,13 @@ public:
static QVersionNumber xcodeVersion();
static Utils::FileName lldbPath();
static void updateAutomaticKitList();
static const DevelopmentTeams &developmentTeams();
static DevelopmentTeamPtr developmentTeam(const QString &teamID);
static const ProvisioningProfiles &provisioningProfiles();
static ProvisioningProfilePtr provisioningProfile(const QString &profileID);
signals:
void provisioningDataChanged();
private:
IosConfigurations(QObject *parent);
@@ -71,11 +129,17 @@ private:
void save();
void updateSimulators();
static void setDeveloperPath(const Utils::FileName &devPath);
void initializeProvisioningData();
void loadProvisioningData(bool notify = true);
Utils::FileName m_developerPath;
QVersionNumber m_xcodeVersion;
bool m_ignoreAllDevices;
QFileSystemWatcher *m_provisioningDataWatcher;
ProvisioningProfiles m_provisioningProfiles;
DevelopmentTeams m_developerTeams;
};
QDebug &operator<<(QDebug &stream, std::shared_ptr<ProvisioningProfile> profile);
QDebug &operator<<(QDebug &stream, std::shared_ptr<DevelopmentTeam> team);
} // namespace Internal
} // namespace Ios