From be8cdeafd657d5568e102e2c291ea1741ea897b6 Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Fri, 28 Feb 2020 19:27:03 +0200 Subject: [PATCH] Android: Allow adding OpenSSL libs directly from project settings This serves as a convenience addition to allow users to directly include OpenSSL prebuilt libs for Android. The path of the OpenSSL would be defined once in the Android options page, and always used to include the libs when needed by the user. How this works: 1- A download button is provided, it first tries to automatically git clone the OpenSSL repo to the defined path. If the cloning fails, the repo URL is opened externally for maunual download. 2- If SDK tools auto download is used (like for first time setup), the OpenSSL download will start after SDK eseentials are installed. 3- Once the libs path is set, it can be used by AndroidBuildApkWidget to include() function to the project (qmake/cmake). It also, should detect if the include() part already exists in the project file. Task-number: QTBUG-80625 Change-Id: I338e916f03f4ff55db25a118f1ea08f1da5dd103 Reviewed-by: hjk --- src/plugins/android/androidbuildapkwidget.cpp | 68 ++++++++- src/plugins/android/androidbuildapkwidget.h | 4 + src/plugins/android/androidconfigurations.cpp | 14 ++ src/plugins/android/androidconfigurations.h | 5 + src/plugins/android/androidsettingswidget.cpp | 130 +++++++++++++++++- src/plugins/android/androidsettingswidget.ui | 42 ++++++ 6 files changed, 255 insertions(+), 8 deletions(-) diff --git a/src/plugins/android/androidbuildapkwidget.cpp b/src/plugins/android/androidbuildapkwidget.cpp index 4e2a9f128b3..66be50dca7d 100644 --- a/src/plugins/android/androidbuildapkwidget.cpp +++ b/src/plugins/android/androidbuildapkwidget.cpp @@ -35,6 +35,7 @@ #include "createandroidmanifestwizard.h" #include +#include #include #include #include @@ -273,8 +274,11 @@ QWidget *AndroidBuildApkWidget::createAdditionalLibrariesGroup() group->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); auto libsModel = new AndroidExtraLibraryListModel(m_step->target(), this); - connect(libsModel, &AndroidExtraLibraryListModel::enabledChanged, - group, &QWidget::setEnabled); + connect(libsModel, &AndroidExtraLibraryListModel::enabledChanged, this, + [this, group](const bool enabled) { + group->setEnabled(enabled); + m_openSslCheckBox->setChecked(isOpenSslLibsIncluded()); + }); auto libsView = new QListView; libsView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -308,9 +312,16 @@ QWidget *AndroidBuildApkWidget::createAdditionalLibrariesGroup() libsButtonLayout->addWidget(removeLibButton); libsButtonLayout->addStretch(1); - auto hbox = new QHBoxLayout(group); - hbox->addWidget(libsView); - hbox->addLayout(libsButtonLayout); + m_openSslCheckBox = new QCheckBox(tr("Include prebuilt OpenSSL libraries")); + m_openSslCheckBox->setToolTip(tr("This is useful for apps that use SSL operations. The path " + "can be defined in Tools > Options > Devices > Android.")); + connect(m_openSslCheckBox, &QAbstractButton::clicked, this, + &AndroidBuildApkWidget::onOpenSslCheckBoxChanged); + + auto grid = new QGridLayout(group); + grid->addWidget(m_openSslCheckBox, 0, 0); + grid->addWidget(libsView, 1, 0); + grid->addLayout(libsButtonLayout, 1, 1); QItemSelectionModel *libSelection = libsView->selectionModel(); connect(libSelection, &QItemSelectionModel::selectionChanged, this, [libSelection, removeLibButton] { @@ -337,6 +348,53 @@ void AndroidBuildApkWidget::signPackageCheckBoxToggled(bool checked) setCertificates(); } +void AndroidBuildApkWidget::onOpenSslCheckBoxChanged() +{ + Utils::FilePath projectPath = m_step->buildConfiguration()->buildSystem()->projectFilePath(); + QFile projectFile(projectPath.toString()); + if (!projectFile.open(QIODevice::ReadWrite | QIODevice::Text)) { + qWarning() << "Cound't open project file to add OpenSSL extra libs: " << projectPath; + return; + } + + const QString searchStr = openSslIncludeFileContent(projectPath); + QTextStream textStream(&projectFile); + + QString fileContent = textStream.readAll(); + if (!m_openSslCheckBox->isChecked()) { + fileContent.remove("\n" + searchStr); + } else if (!fileContent.contains(searchStr, Qt::CaseSensitive)) { + fileContent.append(searchStr + "\n"); + } + + projectFile.resize(0); + textStream << fileContent; + projectFile.close(); +} + +bool AndroidBuildApkWidget::isOpenSslLibsIncluded() +{ + Utils::FilePath projectPath = m_step->buildConfiguration()->buildSystem()->projectFilePath(); + const QString searchStr = openSslIncludeFileContent(projectPath); + QFile projectFile(projectPath.toString()); + projectFile.open(QIODevice::ReadOnly); + QTextStream textStream(&projectFile); + QString fileContent = textStream.readAll(); + projectFile.close(); + return fileContent.contains(searchStr, Qt::CaseSensitive); +} + +QString AndroidBuildApkWidget::openSslIncludeFileContent(const Utils::FilePath &projectPath) +{ + QString openSslPath = AndroidConfigurations::currentConfig().openSslLocation().toString(); + if (projectPath.endsWith(".pro")) + return "android: include(" + openSslPath + "/openssl.pri)"; + if (projectPath.endsWith("CMakeLists.txt")) + return "if (ANDROID)\n include(" + openSslPath + "/CMakeLists.txt)\nendif()"; + + return QString(); +} + void AndroidBuildApkWidget::setCertificates() { QAbstractItemModel *certificates = m_step->keystoreCertificates(); diff --git a/src/plugins/android/androidbuildapkwidget.h b/src/plugins/android/androidbuildapkwidget.h index c3751484a19..5f6875abe25 100644 --- a/src/plugins/android/androidbuildapkwidget.h +++ b/src/plugins/android/androidbuildapkwidget.h @@ -56,6 +56,9 @@ private: void setCertificates(); void updateSigningWarning(); void signPackageCheckBoxToggled(bool checked); + void onOpenSslCheckBoxChanged(); + bool isOpenSslLibsIncluded(); + QString openSslIncludeFileContent(const Utils::FilePath &projectPath); QWidget *createApplicationGroup(); QWidget *createSignPackageGroup(); @@ -69,6 +72,7 @@ private: Utils::InfoLabel *m_signingDebugWarningLabel = nullptr; QComboBox *m_certificatesAliasComboBox = nullptr; QCheckBox *m_addDebuggerCheckBox = nullptr; + QCheckBox *m_openSslCheckBox = nullptr; }; } // namespace Internal diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 5879c0e0073..e9029ce1182 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -104,6 +104,7 @@ namespace { const QLatin1String SdkFullyConfiguredKey("AllEssentialsInstalled"); const QLatin1String SDKManagerToolArgsKey("SDKManagerToolArgs"); const QLatin1String OpenJDKLocationKey("OpenJDKLocation"); + const QLatin1String OpenSslPriLocationKey("OpenSSLPriLocation"); const QLatin1String KeystoreLocationKey("KeystoreLocation"); const QLatin1String AutomaticKitCreationKey("AutomatiKitCreation"); const QLatin1String PartitionSizeKey("PartitionSize"); @@ -239,6 +240,7 @@ void AndroidConfig::load(const QSettings &settings) m_customNdkList = settings.value(CustomNdkLocationsKey).toStringList(); m_sdkManagerToolArgs = settings.value(SDKManagerToolArgsKey).toStringList(); m_openJDKLocation = FilePath::fromString(settings.value(OpenJDKLocationKey).toString()); + m_openSslLocation = FilePath::fromString(settings.value(OpenSslPriLocationKey).toString()); m_keystoreLocation = FilePath::fromString(settings.value(KeystoreLocationKey).toString()); m_automaticKitCreation = settings.value(AutomaticKitCreationKey, true).toBool(); m_sdkFullyConfigured = settings.value(SdkFullyConfiguredKey, false).toBool(); @@ -251,6 +253,7 @@ void AndroidConfig::load(const QSettings &settings) m_customNdkList = reader.restoreValue(CustomNdkLocationsKey).toStringList(); m_sdkManagerToolArgs = reader.restoreValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs).toStringList(); m_openJDKLocation = FilePath::fromString(reader.restoreValue(OpenJDKLocationKey, m_openJDKLocation.toString()).toString()); + m_openSslLocation = FilePath::fromString(reader.restoreValue(OpenSslPriLocationKey, m_openSslLocation.toString()).toString()); m_automaticKitCreation = reader.restoreValue(AutomaticKitCreationKey, m_automaticKitCreation).toBool(); m_sdkFullyConfigured = reader.restoreValue(SdkFullyConfiguredKey, m_sdkFullyConfigured).toBool(); // persistent settings @@ -271,6 +274,7 @@ void AndroidConfig::save(QSettings &settings) const settings.setValue(SDKManagerToolArgsKey, m_sdkManagerToolArgs); settings.setValue(OpenJDKLocationKey, m_openJDKLocation.toString()); settings.setValue(KeystoreLocationKey, m_keystoreLocation.toString()); + settings.setValue(OpenSslPriLocationKey, m_openSslLocation.toString()); settings.setValue(PartitionSizeKey, m_partitionSize); settings.setValue(AutomaticKitCreationKey, m_automaticKitCreation); settings.setValue(SdkFullyConfiguredKey, m_sdkFullyConfigured); @@ -374,6 +378,16 @@ void AndroidConfig::removeCustomNdk(const QString &customNdk) m_customNdkList.removeAll(customNdk); } +Utils::FilePath AndroidConfig::openSslLocation() const +{ + return m_openSslLocation; +} + +void AndroidConfig::setOpenSslLocation(const Utils::FilePath &openSslLocation) +{ + m_openSslLocation = openSslLocation; +} + QStringList AndroidConfig::apiLevelNamesFor(const SdkPlatformList &platforms) { return Utils::transform(platforms, AndroidConfig::apiLevelNameFor); diff --git a/src/plugins/android/androidconfigurations.h b/src/plugins/android/androidconfigurations.h index f793ed78c3f..cf324cdcfb5 100644 --- a/src/plugins/android/androidconfigurations.h +++ b/src/plugins/android/androidconfigurations.h @@ -196,6 +196,10 @@ public: void addCustomNdk(const QString &customNdk); void removeCustomNdk(const QString &customNdk); + Utils::FilePath openSslLocation() const; + void setOpenSslLocation(const Utils::FilePath &openSslLocation); + + private: static QString getDeviceProperty(const Utils::FilePath &adbToolPath, const QString &device, const QString &property); @@ -217,6 +221,7 @@ private: QStringList m_sdkManagerToolArgs; Utils::FilePath m_openJDKLocation; Utils::FilePath m_keystoreLocation; + Utils::FilePath m_openSslLocation; unsigned m_partitionSize = 1024; bool m_automaticKitCreation = true; QUrl m_sdkToolsUrl; diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp index 42511b893c3..30875271c10 100644 --- a/src/plugins/android/androidsettingswidget.cpp +++ b/src/plugins/android/androidsettingswidget.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -112,6 +113,7 @@ private: void openSDKDownloadUrl(); void openNDKDownloadUrl(); void openOpenJDKDownloadUrl(); + void downloadOpenSslRepo(const bool silent = false); void addAVD(); void avdAdded(); void removeAVD(); @@ -134,6 +136,7 @@ private: Utils::FilePath getDefaultSdkPath(); void showEvent(QShowEvent *event) override; void addCustomNdkItem(); + void validateOpenSsl(); Ui_AndroidSettingsWidget *m_ui; AndroidSdkManagerWidget *m_sdkManagerWidget = nullptr; @@ -167,6 +170,12 @@ enum AndroidValidation { NdkinstallDirOkRow }; +enum OpenSslValidation { + OpenSslPathExistsRow, + OpenSslPriPathExists, + OpenSslCmakeListsPathExists +}; + class SummaryWidget : public QWidget { class RowData { @@ -344,6 +353,7 @@ void AndroidSettingsWidget::showEvent(QShowEvent *event) // to let settings dialog open first. QTimer::singleShot(0, std::bind(&AndroidSdkManager::reloadPackages, m_sdkManager.get(), false)); + validateOpenSsl(); m_isInitialReloadDone = true; } } @@ -421,6 +431,18 @@ AndroidSettingsWidget::AndroidSettingsWidget() m_ui->androidDetailsWidget); m_ui->androidDetailsWidget->setWidget(androidSummary); + QMap openSslValidationPoints; + openSslValidationPoints[OpenSslPathExistsRow] = tr("OpenSSL path exists."); + openSslValidationPoints[OpenSslPriPathExists] = tr( + "QMake include project (openssl.pri) exists."); + openSslValidationPoints[OpenSslCmakeListsPathExists] = tr( + "CMake include project (CMakeLists.txt) exists."); + auto openSslSummary = new SummaryWidget(openSslValidationPoints, + tr("OpenSSL Settings are OK."), + tr("OpenSSL settings have errors."), + m_ui->openSslDetailsWidget); + m_ui->openSslDetailsWidget->setWidget(openSslSummary); + connect(m_ui->OpenJDKLocationPathChooser, &Utils::PathChooser::rawPathChanged, this, &AndroidSettingsWidget::validateJdk); Utils::FilePath currentJdkPath = m_androidConfig.openJDKLocation(); @@ -436,6 +458,12 @@ AndroidSettingsWidget::AndroidSettingsWidget() m_ui->SDKLocationPathChooser->setFileName(currentSDKPath); m_ui->SDKLocationPathChooser->setPromptDialogTitle(tr("Select Android SDK folder")); + m_ui->openSslPathChooser->setPromptDialogTitle(tr("Select OpenSSL Include Project File")); + Utils::FilePath currentOpenSslPath = m_androidConfig.openSslLocation(); + if (currentOpenSslPath.isEmpty()) + currentOpenSslPath = currentSDKPath.pathAppended("android_openssl"); + m_ui->openSslPathChooser->setFileName(currentOpenSslPath); + m_ui->DataPartitionSizeSpinBox->setValue(m_androidConfig.partitionSize()); m_ui->CreateKitCheckBox->setChecked(m_androidConfig.automaticKitCreation()); m_ui->AVDTableView->setModel(&m_AVDModel); @@ -448,6 +476,7 @@ AndroidSettingsWidget::AndroidSettingsWidget() m_ui->downloadSDKToolButton->setIcon(downloadIcon); m_ui->downloadNDKToolButton->setIcon(downloadIcon); m_ui->downloadOpenJDKToolButton->setIcon(downloadIcon); + m_ui->downloadOpenSSLPrebuiltLibs->setIcon(downloadIcon); m_ui->sdkToolsAutoDownloadButton->setIcon(Utils::Icons::RELOAD.icon()); connect(m_ui->SDKLocationPathChooser, &Utils::PathChooser::rawPathChanged, @@ -469,6 +498,10 @@ AndroidSettingsWidget::AndroidSettingsWidget() m_ui->ndkListComboBox->removeItem(m_ui->ndkListComboBox->currentIndex()); }); + connect(m_ui->ndkListComboBox, QOverload::of(&QComboBox::currentIndexChanged), + [this](const QString) { validateNdk(); }); + connect(m_ui->openSslPathChooser, &Utils::PathChooser::rawPathChanged, this, + &AndroidSettingsWidget::validateOpenSsl); connect(&m_virtualDevicesWatcher, &QFutureWatcherBase::finished, this, &AndroidSettingsWidget::updateAvds); connect(m_ui->AVDRefreshPushButton, &QAbstractButton::clicked, @@ -494,6 +527,8 @@ AndroidSettingsWidget::AndroidSettingsWidget() this, &AndroidSettingsWidget::openNDKDownloadUrl); connect(m_ui->downloadSDKToolButton, &QAbstractButton::clicked, this, &AndroidSettingsWidget::openSDKDownloadUrl); + connect(m_ui->downloadOpenSSLPrebuiltLibs, &QAbstractButton::clicked, + this, &AndroidSettingsWidget::downloadOpenSslRepo); connect(m_ui->downloadOpenJDKToolButton, &QAbstractButton::clicked, this, &AndroidSettingsWidget::openOpenJDKDownloadUrl); // Validate SDK again after any change in SDK packages. @@ -564,6 +599,22 @@ void AndroidSettingsWidget::validateJdk() updateUI(); } +void AndroidSettingsWidget::validateOpenSsl() +{ + auto openSslPath = Utils::FilePath::fromUserInput(m_ui->openSslPathChooser->rawPath()); + m_androidConfig.setOpenSslLocation(openSslPath); + + auto summaryWidget = static_cast(m_ui->openSslDetailsWidget->widget()); + summaryWidget->setPointValid(OpenSslPathExistsRow, m_androidConfig.openSslLocation().exists()); + + const bool priFileExists = m_androidConfig.openSslLocation().pathAppended("openssl.pri").exists(); + summaryWidget->setPointValid(OpenSslPriPathExists, priFileExists); + const bool cmakeListsExists + = m_androidConfig.openSslLocation().pathAppended("CMakeLists.txt").exists(); + summaryWidget->setPointValid(OpenSslCmakeListsPathExists, cmakeListsExists); + updateUI(); +} + Utils::FilePath AndroidSettingsWidget::findJdkInCommonPaths() { QString jdkFromEnvVar = QString::fromLocal8Bit(getenv("JAVA_HOME")); @@ -700,6 +751,67 @@ void AndroidSettingsWidget::openOpenJDKDownloadUrl() QDesktopServices::openUrl(QUrl::fromUserInput("http://www.oracle.com/technetwork/java/javase/downloads/")); } +void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent) +{ + const Utils::FilePath openSslPath = m_ui->openSslPathChooser->fileName(); + const QString openSslCloneTitle(tr("OpenSSL Cloning")); + + auto openSslSummaryWidget = static_cast(m_ui->openSslDetailsWidget->widget()); + if (openSslSummaryWidget->allRowsOk()) { + if (silent) { + QMessageBox::information(this, openSslCloneTitle, + tr("OpenSSL prebuilt libraries repository is already configured.")); + } + return; + } + + const QString openSslRepo("https://github.com/KDAB/android_openssl.git"); + Utils::QtcProcess *gitCloner = new Utils::QtcProcess(this); + gitCloner->setCommand(Utils::CommandLine("git", {"clone", openSslRepo, openSslPath.fileName()})); + gitCloner->setWorkingDirectory(openSslPath.parentDir().toString()); + + QDir openSslDir(openSslPath.toString()); + if (openSslDir.exists()) { + auto userInput = QMessageBox::information(this, openSslCloneTitle, + tr("The selected download path (%1) for OpenSSL already exists, " + "do you want to remove and overwrite its content?") + .arg(QDir::toNativeSeparators(openSslPath.toString())), + QMessageBox::Yes | QMessageBox::No); + if (userInput == QMessageBox::Yes) + openSslDir.removeRecursively(); + else + return; + } + + QProgressDialog *openSslProgressDialog + = new QProgressDialog(tr("Cloning OpenSSL prebuilt libraries, please be patient..."), + tr("Cancel"), 0, 0); + openSslProgressDialog->setWindowModality(Qt::WindowModal); + openSslProgressDialog->setWindowTitle(openSslCloneTitle); + openSslProgressDialog->setFixedSize(openSslProgressDialog->sizeHint()); + + connect(openSslProgressDialog, &QProgressDialog::canceled, this, [gitCloner]() { + gitCloner->kill(); + }); + + gitCloner->start(); + openSslProgressDialog->show(); + + connect(gitCloner, QOverload::of(&Utils::QtcProcess::finished), + [=](int exitCode, QProcess::ExitStatus exitStatus) { + openSslProgressDialog->close(); + validateOpenSsl(); + + if (!openSslProgressDialog->wasCanceled() || + (exitStatus == Utils::QtcProcess::NormalExit && exitCode != 0)) { + QMessageBox::information(this, openSslCloneTitle, + tr("OpenSSL prebuilt libraries cloning failed. " + "Opening OpenSSL URL for manual download...")); + QDesktopServices::openUrl(QUrl::fromUserInput(openSslRepo)); + } + }); +} + void AndroidSettingsWidget::addAVD() { disableAvdControls(); @@ -767,9 +879,11 @@ void AndroidSettingsWidget::updateUI() { auto javaSummaryWidget = static_cast(m_ui->javaDetailsWidget->widget()); auto androidSummaryWidget = static_cast(m_ui->androidDetailsWidget->widget()); - bool javaSetupOk = javaSummaryWidget->allRowsOk(); - bool sdkToolsOk = androidSummaryWidget->rowsOk({SdkPathExistsRow, SdkPathWritableRow, SdkToolsInstalledRow}); - bool androidSetupOk = androidSummaryWidget->allRowsOk(); + auto openSslSummaryWidget = static_cast(m_ui->openSslDetailsWidget->widget()); + const bool javaSetupOk = javaSummaryWidget->allRowsOk(); + const bool sdkToolsOk = androidSummaryWidget->rowsOk({SdkPathExistsRow, SdkPathWritableRow, SdkToolsInstalledRow}); + const bool androidSetupOk = androidSummaryWidget->allRowsOk(); + const bool openSslOk = openSslSummaryWidget->allRowsOk(); m_ui->avdManagerTab->setEnabled(javaSetupOk && androidSetupOk); m_ui->sdkManagerTab->setEnabled(sdkToolsOk); @@ -785,6 +899,8 @@ void AndroidSettingsWidget::updateUI() Utils::DetailsWidget::Expanded); m_ui->androidDetailsWidget->setState(androidSetupOk ? Utils::DetailsWidget::Collapsed : Utils::DetailsWidget::Expanded); + m_ui->openSslDetailsWidget->setState(openSslOk ? Utils::DetailsWidget::Collapsed : + Utils::DetailsWidget::Expanded); } void AndroidSettingsWidget::manageAVD() @@ -820,6 +936,14 @@ void AndroidSettingsWidget::downloadSdk() m_sdkManager->reloadPackages(true); updateUI(); apply(); + + QMetaObject::Connection *const openSslOneShot = new QMetaObject::Connection; + *openSslOneShot = connect(m_sdkManager.get(), &AndroidSdkManager::packageReloadFinished, + this, [this, openSslOneShot]() { + QObject::disconnect(*openSslOneShot); + downloadOpenSslRepo(true); + delete openSslOneShot; + }); }); auto showErrorDialog = [this](const QString &error) { diff --git a/src/plugins/android/androidsettingswidget.ui b/src/plugins/android/androidsettingswidget.ui index 6d8272b7dfa..2a25b93386c 100644 --- a/src/plugins/android/androidsettingswidget.ui +++ b/src/plugins/android/androidsettingswidget.ui @@ -204,6 +204,48 @@ + + + + Android OpenSSL settings + + + + + + + 0 + 0 + + + + OpenSSL .pri location: + + + + + + + Select the path of the prebuilt OpenSSL binaries. + + + + + + + + + + Automatically download OpenSSL prebuilt libraries. If the automatic download fails, a URL will be opened in the browser for manual download. + + + + + + + + +