Say hello to Android CMake support

Requirements:
 - NDKr19 or newer
 - Qt 5.12.1 or newer

QtCreator supports the following variables:
 - ANDROID_PACKAGE_SOURCE_DIR
 - ANDROID_EXTRA_LIBS

Be aware, that there is a lot of magic done on QtCreator side, and you
can't use only cmake to build an Android APK.

[ChangeLog][Android][CMake] Add Android support for CMake projects.

Change-Id: I1d351976ed56f424c2bc972f4ff7b5968147a2ed
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
BogDan Vatra
2019-03-06 14:17:17 +02:00
parent a8ebc37da2
commit 26463a2219
15 changed files with 327 additions and 22 deletions

View File

@@ -34,6 +34,8 @@
#include "cmakeprojectmanager.h"
#include "cmakeprojectnodes.h"
#include <android/androidconstants.h>
#include <coreplugin/icore.h>
#include <projectexplorer/buildinfo.h>
@@ -91,6 +93,37 @@ void CMakeBuildConfiguration::initialize(const BuildInfo &info)
BuildStepList *buildSteps = stepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD);
buildSteps->appendStep(new CMakeBuildStep(buildSteps));
if (DeviceTypeKitAspect::deviceTypeId(target()->kit())
== Android::Constants::ANDROID_DEVICE_TYPE) {
buildSteps->appendStep(Android::Constants::ANDROID_BUILD_APK_ID);
const auto &bs = buildSteps->steps().constLast();
m_initialConfiguration.prepend(CMakeProjectManager::CMakeConfigItem{"ANDROID_NATIVE_API_LEVEL",
CMakeProjectManager::CMakeConfigItem::Type::STRING,
"Android native API level",
bs->data(Android::Constants::AndroidNdkPlatform).toString().toUtf8()});
auto ndkLocation = bs->data(Android::Constants::NdkLocation).value<FileName>();
m_initialConfiguration.prepend(CMakeProjectManager::CMakeConfigItem{"ANDROID_NDK",
CMakeProjectManager::CMakeConfigItem::Type::PATH,
"Android NDK PATH",
ndkLocation.toUserOutput().toUtf8()});
m_initialConfiguration.prepend(CMakeProjectManager::CMakeConfigItem{"CMAKE_TOOLCHAIN_FILE",
CMakeProjectManager::CMakeConfigItem::Type::PATH,
"Android CMake toolchain file",
ndkLocation.appendPath("build/cmake/android.toolchain.cmake").toUserOutput().toUtf8()});
m_initialConfiguration.prepend(CMakeProjectManager::CMakeConfigItem{"ANDROID_ABI",
CMakeProjectManager::CMakeConfigItem::Type::STRING,
"Android ABI",
bs->data(Android::Constants::AndroidABI).toString().toUtf8()});
m_initialConfiguration.prepend(CMakeProjectManager::CMakeConfigItem{"ANDROID_STL",
CMakeProjectManager::CMakeConfigItem::Type::STRING,
"Android STL",
"c++_shared"});
m_initialConfiguration.prepend(CMakeProjectManager::CMakeConfigItem{"CMAKE_FIND_ROOT_PATH_MODE_PROGRAM", "BOTH"});
m_initialConfiguration.prepend(CMakeProjectManager::CMakeConfigItem{"CMAKE_FIND_ROOT_PATH_MODE_LIBRARY", "BOTH"});
m_initialConfiguration.prepend(CMakeProjectManager::CMakeConfigItem{"CMAKE_FIND_ROOT_PATH_MODE_INCLUDE", "BOTH"});
m_initialConfiguration.prepend(CMakeProjectManager::CMakeConfigItem{"CMAKE_FIND_ROOT_PATH_MODE_PACKAGE", "BOTH"});
}
BuildStepList *cleanSteps = stepList(ProjectExplorer::Constants::BUILDSTEPS_CLEAN);
cleanSteps->appendStep(new CMakeBuildStep(cleanSteps));
@@ -162,19 +195,19 @@ bool CMakeBuildConfiguration::isParsing() const
BuildTargetInfoList CMakeBuildConfiguration::appTargets() const
{
BuildTargetInfoList appTargetList;
bool forAndroid = DeviceTypeKitAspect::deviceTypeId(target()->kit()) == Android::Constants::ANDROID_DEVICE_TYPE;
for (const CMakeBuildTarget &ct : m_buildTargets) {
if (ct.targetType == UtilityType)
continue;
if (ct.targetType == ExecutableType) {
if (ct.targetType == ExecutableType || (forAndroid && ct.targetType == DynamicLibraryType)) {
BuildTargetInfo bti;
bti.displayName = ct.title;
bti.targetFilePath = ct.executable;
bti.projectFilePath = ct.sourceDirectory;
bti.projectFilePath.appendString('/');
bti.workingDirectory = ct.workingDirectory;
bti.buildKey = ct.title + QChar('\n') + bti.projectFilePath.toString();
bti.buildKey = CMakeTargetNode::generateId(ct.sourceDirectory, ct.title);
appTargetList.list.append(bti);
}
}
@@ -220,6 +253,11 @@ QStringList CMakeBuildConfiguration::buildTargetTitles() const
return transform(m_buildTargets, &CMakeBuildTarget::title);
}
const QList<CMakeBuildTarget> &CMakeBuildConfiguration::buildTargets() const
{
return m_buildTargets;
}
FileName CMakeBuildConfiguration::shadowBuildDirectory(const FileName &projectFilePath,
const Kit *k,
const QString &bcName,
@@ -341,7 +379,11 @@ static CMakeConfig removeDuplicates(const CMakeConfig &config)
void CMakeBuildConfiguration::setConfigurationForCMake(const CMakeConfig &config)
{
m_configurationForCMake = removeDuplicates(config);
auto configs = removeDuplicates(config);
if (m_configurationForCMake.isEmpty())
m_configurationForCMake = removeDuplicates(configs + m_initialConfiguration);
else
m_configurationForCMake = configs;
const Kit *k = target()->kit();
CMakeConfig kitConfig = CMakeConfigurationKitAspect::configuration(k);

View File

@@ -63,6 +63,7 @@ public:
QString warning() const;
QStringList buildTargetTitles() const;
const QList<CMakeBuildTarget> &buildTargets() const;
ProjectExplorer::BuildTargetInfoList appTargets() const;
ProjectExplorer::DeploymentData deploymentData() const;
@@ -72,7 +73,6 @@ public:
// Context menu action:
void buildTarget(const QString &buildTarget);
signals:
void errorOccured(const QString &message);
void warningOccured(const QString &message);
@@ -104,6 +104,7 @@ private:
void setWarning(const QString &message);
CMakeConfig m_configurationForCMake;
CMakeConfig m_initialConfiguration;
QString m_error;
QString m_warning;

View File

@@ -265,13 +265,56 @@ void CMakeProject::updateProjectData(CMakeBuildConfiguration *bc)
QTC_ASSERT(bc == aBc, return);
QTC_ASSERT(m_treeScanner.isFinished() && !m_buildDirManager.isParsing(), return);
bc->setBuildTargets(m_buildDirManager.takeBuildTargets());
bc->setConfigurationFromCMake(m_buildDirManager.takeCMakeConfiguration());
const QList<CMakeBuildTarget> buildTargets = m_buildDirManager.takeBuildTargets();
bc->setBuildTargets(buildTargets);
const CMakeConfig cmakeConfig = m_buildDirManager.takeCMakeConfiguration();
bc->setConfigurationFromCMake(cmakeConfig);
CMakeConfig patchedConfig = cmakeConfig;
{
CMakeConfigItem settingFileItem;
settingFileItem.key = "ANDROID_DEPLOYMENT_SETTINGS_FILE";
settingFileItem.value = bc->buildDirectory().appendPath("android_deployment_settings.json").toString().toUtf8();
patchedConfig.append(settingFileItem);
}
QSet<QString> res;
QStringList apps;
for (const auto &target : bc->buildTargets()) {
if (target.targetType == CMakeProjectManager::DynamicLibraryType) {
res.insert(target.executable.parentDir().toString());
apps.push_back(target.executable.toUserOutput());
}
// ### shall we add also the ExecutableType ?
}
{
CMakeConfigItem paths;
paths.key = "ANDROID_SO_LIBS_PATHS";
paths.values = res.toList();
patchedConfig.append(paths);
}
apps.sort();
{
CMakeConfigItem appsPaths;
appsPaths.key = "TARGETS_BUILD_PATH";
appsPaths.values = apps;
patchedConfig.append(appsPaths);
}
auto newRoot = generateProjectTree(m_allFiles);
if (newRoot) {
setDisplayName(newRoot->displayName());
setRootProjectNode(std::move(newRoot));
for (const CMakeBuildTarget &bt : buildTargets) {
const QString buildKey = CMakeTargetNode::generateId(bt.sourceDirectory, bt.title);
if (ProjectNode *node = findNodeForBuildKey(buildKey)) {
if (auto targetNode = dynamic_cast<CMakeTargetNode *>(node))
targetNode->setConfig(patchedConfig);
}
}
}
Target *t = bc->target();

View File

@@ -25,22 +25,31 @@
#include "cmakeprojectnodes.h"
#include "cmakeconfigitem.h"
#include "cmakeprojectconstants.h"
#include "cmakeprojectplugin.h"
#include <android/androidconstants.h>
#include <coreplugin/fileiconprovider.h>
#include <coreplugin/icore.h>
#include <cpptools/cpptoolsconstants.h>
#include <projectexplorer/target.h>
#include <utils/algorithm.h>
#include <utils/checkablemessagebox.h>
#include <utils/mimetypes/mimedatabase.h>
#include <utils/optional.h>
#include <utils/qtcassert.h>
#include <QClipboard>
#include <QDir>
#include <QGuiApplication>
#include <QMessageBox>
using namespace ProjectExplorer;
using namespace CMakeProjectManager;
using namespace CMakeProjectManager::Internal;
@@ -159,8 +168,9 @@ bool CMakeProjectNode::addFiles(const QStringList &filePaths, QStringList *)
}
CMakeTargetNode::CMakeTargetNode(const Utils::FileName &directory, const QString &target) :
ProjectExplorer::ProjectNode(directory), m_target(target)
ProjectExplorer::ProjectNode(directory)
{
m_target = target;
setPriority(Node::DefaultProjectPriority + 900);
setIcon(QIcon(":/projectexplorer/images/build.png")); // TODO: Use proper icon!
setListInProject(false);
@@ -181,6 +191,52 @@ QString CMakeTargetNode::buildKey() const
return generateId(filePath(), m_target);
}
QVariant CMakeTargetNode::data(Core::Id role) const
{
auto value = [this](const QByteArray &key) -> QVariant {
for (const CMakeConfigItem &configItem : m_config) {
if (configItem.key == key)
return configItem.value;
}
return {};
};
auto values = [this](const QByteArray &key) -> QVariant {
for (const CMakeConfigItem &configItem : m_config) {
if (configItem.key == key)
return configItem.values;
}
return {};
};
if (role == Android::Constants::AndroidPackageSourceDir)
return value("ANDROID_PACKAGE_SOURCE_DIR");
if (role == Android::Constants::AndroidDeploySettingsFile)
return value("ANDROID_DEPLOYMENT_SETTINGS_FILE");
if (role == Android::Constants::AndroidExtraLibs)
return value("ANDROID_EXTRA_LIBS");
if (role == Android::Constants::AndroidArch)
return value("ANDROID_ABI");
if (role == Android::Constants::AndroidSoLibPath)
return values("ANDROID_SO_LIBS_PATHS");
if (role == Android::Constants::AndroidTargets)
return values("TARGETS_BUILD_PATH");
QTC_CHECK(false);
// Better guess than "not present".
return value(role.toString().toUtf8());
}
void CMakeTargetNode::setConfig(const CMakeConfig &config)
{
m_config = config;
}
bool CMakeTargetNode::supportsAction(ProjectExplorer::ProjectAction action,
const ProjectExplorer::Node *) const
{

View File

@@ -25,6 +25,8 @@
#pragma once
#include "cmakeconfigitem.h"
#include <projectexplorer/projectnodes.h>
namespace CMakeProjectManager {
@@ -72,9 +74,12 @@ public:
bool addFiles(const QStringList &filePaths, QStringList *notAdded) override;
Utils::optional<Utils::FileName> visibleAfterAddFileAction() const override;
QVariant data(Core::Id role) const override;
void setConfig(const CMakeConfig &config);
private:
QString m_tooltip;
QString m_target;
CMakeConfig m_config;
};
} // namespace Internal