Skip to content

Instantly share code, notes, and snippets.

@CAMOBAP
Last active March 25, 2024 14:57
Show Gist options
  • Save CAMOBAP/ed9aa6a7549787b5eea4b2048b896747 to your computer and use it in GitHub Desktop.
Save CAMOBAP/ed9aa6a7549787b5eea4b2048b896747 to your computer and use it in GitHub Desktop.
include(default)
standalone_toolchain=/opt/android-ndk-toolchains/arm-27
ndk_home=/opt/android-ndk
api_level=27
target_host=arm-linux-androideabi
cc_compiler=clang
cxx_compiler=clang++
target_specific_flags=-mfpu=neon
[settings]
os=Android
os.api_level=$api_level
arch=armv7hf
compiler=clang
compiler.version=7.0
compiler.libcxx=libstdc++
[env]
ANDROID_NDK_HOME=$ndk_home
CONAN_CMAKE_FIND_ROOT_PATH=$standalone_toolchain/sysroot
PATH=[$standalone_toolchain/bin]
CHOST=$target_host
AR=$target_host-ar
AS=$target_host-as
RANLIB=$target_host-ranlib
CC=$target_host-$cc_compiler
CXX=$target_host-$cxx_compiler
LD=$target_host-ld
STRIP=$target_host-strip
CFLAGS= -fPIE -fPIC -I$standalone_toolchain/include/c++/4.9.x -D__ANDROID__ -D__ANDROID_API__=$api_level $target_specific_flags
CXXFLAGS= -fPIE -fPIC -I$standalone_toolchain/include/c++/4.9.x -D__ANDROID__ -D__ANDROID_API__=$api_level $target_specific_flags
LDFLAGS= -pie -march=armv7-a -Wl,--fix-cortex-a8
standalone_toolchain=/opt/android-ndk-toolchains/x86_64-27
ndk_home=/opt/android-ndk
api_level=27
target_host=x86_64-linux-android
cc_compiler=clang
cxx_compiler=clang++
target_specific_flags=-mfpu=neon
[settings]
os=Android
os.api_level=$api_level
arch=x86_64
compiler=clang
compiler.version=7.0
compiler.libcxx=libstdc++
[env]
ANDROID_NDK_HOME=$ndk_home
CONAN_CMAKE_FIND_ROOT_PATH=$standalone_toolchain/sysroot
PATH=[$standalone_toolchain/bin]
CHOST=$target_host
AR=$target_host-ar
AS=$target_host-as
RANLIB=$target_host-ranlib
CC=$target_host-$cc_compiler
CXX=$target_host-$cxx_compiler
LD=$target_host-ld
STRIP=$target_host-strip
CFLAGS= -fPIE -fPIC -I$standalone_toolchain/include/c++/4.9.x -D__ANDROID__ -D__ANDROID_API__=$api_level $target_specific_flags
CXXFLAGS= -fPIE -fPIC -I$standalone_toolchain/include/c++/4.9.x -D__ANDROID__ -D__ANDROID_API__=$api_level $target_specific_flags
LDFLAGS= -pie
apply plugin: 'com.android.application'
apply from: "${rootDir}/conan.gradle"
conan {
conanfile = 'src/main/cpp/conanfile.txt'
profile = 'android-${abi}'
}
android {
compileSdkVersion 28
defaultConfig {
applicationId "org.camobap.conan.gradle.example"
minSdkVersion 27
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
abiFilters "armeabi-v7a", "x86_64"
arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_STL=c++_shared",
}
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
}
clean.doLast {
delete file('.externalNativeBuild')
}
dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:support-annotations:28.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:core:1.1.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test:rules:1.1.1'
}
import java.nio.file.Paths
import groovy.text.SimpleTemplateEngine
class ConanPluginExtension {
String conanfile = "src/main/cpp/conanfile.txt"
String profile = 'android-${abi}' // TODO maybe support map abi->filename
String outputDirPath = '${projectDir}/.externalNativeBuild/conan/${flavor}/${abi}'
}
class ConanPlugin implements Plugin<Project> {
def android
def extension
def conanfilePath
def conanProfileFileNameTemplate
def conanOutputDirPathTemplate
void validate(Project project) {
android = project.extensions.android
assert android: "Cannot be applied for non android projects"
conanfilePath = Paths.get(project.projectDir.absolutePath, extension.conanfile).toString()
assert project.file(conanfilePath).exists(): "conan file ${conanfilePath} doesn't exists"
conanProfileFileNameTemplate = extension.profile
conanOutputDirPathTemplate = extension.outputDirPath
}
void createTasksForAndroidExternalNativeBuild(Project project) {
android.applicationVariants.all { variant ->
def engine = new SimpleTemplateEngine()
for (def abi in android.defaultConfig.externalNativeBuild.cmake.abiFilters) {
def flavor = variant.name
def taskSuffix = "${abi.capitalize()}${flavor.capitalize()}"
def buildType = flavor.toLowerCase().contains('release') ? 'release' : 'debug'
def params = ['abi': abi, 'flavor': flavor, 'projectDir': project.projectDir, 'buildType': buildType]
def conanProfileFileName = engine.createTemplate(conanProfileFileNameTemplate).make(params).toString()
def conanOutputDirPath = engine.createTemplate(conanOutputDirPathTemplate).make(params).toString()
def conanInstallTaskName = "conanInstall${taskSuffix}"
def conanInstallTask = project.task(conanInstallTaskName, type: Exec) {
group 'Conan tasks'
description 'Run conan to get and build missing dependencies'
workingDir conanOutputDirPath
commandLine 'conan', 'install', conanfilePath,
'--profile', conanProfileFileName,
'--settings', "build_type=${buildType.capitalize()}",
'--install-folder', workingDir,
'--build', 'missing'
inputs.files conanfilePath
outputs.dir workingDir
doFirst {
workingDir.mkdirs()
}
}
def conanCheckProfileTaskName = "conanCheckProfileFor${taskSuffix}"
project.task(conanCheckProfileTaskName) {
group 'Conan tasks'
description 'Check that conan profile file exists'
doFirst {
if (!project.file(conanProfileFileName).exists()) {
def conanProfilePath = "${System.properties['user.home']}/.conan/profiles/${conanProfileFileName}"
assert project.file(conanProfilePath).exists() \
: "Conan profile file \"${conanProfilePath}\" missing please check README.md"
}
}
}
def conanCleanTaskName = "conanClean${taskSuffix}"
project.task(conanCleanTaskName, type: Delete) {
group 'Conan tasks'
description 'Delete conan generated files'
delete conanOutputDirPath
}
conanInstallTask.dependsOn(conanCheckProfileTaskName)
project.tasks.findByName("externalNativeBuild${flavor.capitalize()}").dependsOn(conanInstallTaskName)
project.tasks.findByName("externalNativeBuildClean${flavor.capitalize()}").dependsOn(conanCleanTaskName)
}
}
}
void apply(Project project) {
extension = project.extensions.create('conan', ConanPluginExtension)
project.afterEvaluate {
validate(project)
createTasksForAndroidExternalNativeBuild(project)
}
}
}
apply plugin: ConanPlugin
@ppetraki
Copy link

@CAMOBAP795

Turns out I really needed to target x86 instead of x86_64 and configure gradle to build a universal apk. It's working now. I've fixed some of the clean target issues by added a dependency on "externalNativeBuildCleanRegularDebug". The other weird thing I'm seeing is it doesn't always regenerate the conan install targets. Sometimes it will drop release, sometimes it adds sastRelease and sastDebug. It's odd.

While my if else hack works. What would be ideal is if the plugin generated a top level buildinfo,cmake file in conan_build/ (or whatever) that accounted for all the build types generated by gradle and how they map to cmake build types. Then we can just include a single path and it will "just work".

That would be sick (⌐■_■)

@CAMOBAP
Copy link
Author

CAMOBAP commented Nov 13, 2019

@ppetraki, my bad there were flavors in build.gradle, please remove them (I have updated build.gradle in this gist)
please run ./gradlew clean build and show the error (you should not rely on sast or regular anymore in paths)

To support x86 please list it in https://gist.github.com/CAMOBAP795/ed9aa6a7549787b5eea4b2048b896747#file-build-gradle-L22

@ppetraki
Copy link

@CAMOBAP795

Thanks.

To build the fat APK you need to add this:

      // https://stackoverflow.com/questions/36414219/install-failed-no-matching-abis-failed-to-extract-native-libraries-res-113    
      splits {    
          abi {    
              enable true    
              reset()    
              include 'x86', 'armeabi-v7a', 'x86_64'    
  //            include defaultConfig.externalNativeBuild.getCmake().getAbiFilters().    
              universalApk true    
          }    
      }    

I tried to reuse the abiFilters array (list) but there's a type error. I just don't know enough Java/Groovy yet to write anything more than shell like code atm :)

@ppetraki
Copy link

ppetraki commented Nov 13, 2019

If flavors don't matter for the NDK build. Then lets dump it's metadata from the conan install path and use the actual build type in the format Cmake expects. Then we can do a direct include like this:.

include(${CMAKE_CURRENT_SOURCE_DIR}/../../../conan_build/${CMAKE_BUILD_TYPE}/${ANDROID_ABI}/conanbuildinfo.cmake)

All that has to change in the plugin is:

diff --git a/conan.gradle b/conan.gradle
index e5a2c88..0857349 100644
--- a/conan.gradle
+++ b/conan.gradle
@@ -9,7 +9,7 @@ import groovy.text.SimpleTemplateEngine
 class ConanPluginExtension {
     String conanfile = "src/main/cpp/conanfile.txt"
     String profile = 'android-${abi}' // TODO maybe support map abi->filename
-    String outputDirPath = '${projectDir}/conan_build/${flavor}/${abi}'
+    String outputDirPath = '${projectDir}/conan_build/${buildType}/${abi}'
 }

Where buildType is defined in the params map as follows
def params = ['abi': abi, 'flavor': flavor, 'projectDir': project.projectDir, 'buildType': buildType.capitalize()]

Now the conan_build tree looks like this:

ppetraki@vanguard:~/Sandbox/Games/conansdl2$ !tree
tree app/conan_build/
app/conan_build/
├── Debug
│   ├── armeabi-v7a
│   │   ├── conanbuildinfo.cmake
│   │   ├── conanbuildinfo.txt
│   │   ├── conaninfo.txt
│   │   ├── conan.lock
│   │   └── graph_info.json
│   ├── x86
│   │   ├── conanbuildinfo.cmake
│   │   ├── conanbuildinfo.txt
│   │   ├── conaninfo.txt
│   │   ├── conan.lock
│   │   └── graph_info.json
│   └── x86_64
│       ├── conanbuildinfo.cmake
│       ├── conanbuildinfo.txt
│       ├── conaninfo.txt
│       ├── conan.lock
│       └── graph_info.json
└── Release
    ├── armeabi-v7a
    │   ├── conanbuildinfo.cmake
    │   ├── conanbuildinfo.txt
    │   ├── conaninfo.txt
    │   ├── conan.lock
    │   └── graph_info.json
    ├── x86
    │   ├── conanbuildinfo.cmake
    │   ├── conanbuildinfo.txt
    │   ├── conaninfo.txt
    │   ├── conan.lock
    │   └── graph_info.json
    └── x86_64
        ├── conanbuildinfo.cmake
        ├── conanbuildinfo.txt
        ├── conaninfo.txt
        ├── conan.lock
        └── graph_info.json

8 directories, 30 files

@ppetraki
Copy link

@CAMOBAP795

I solved the "distclean" problem. It was a matter of different tasks being invoked by the IDE vs the CLI. I've created my own gist now as the plugin has diverged quite a bit from your original version. Feel free to merge the changes.

https://gist.github.com/ppetraki/ca8630cc272edcd6a881ed82aa30b559

I think this can be done with even less external config. Especially if you use the cmake_wrapper. As all of my profiles vary by just a single config string that could easily be driven to the conan CLI. Then the plugin could provide it's own default NDK profile and decorate it. Making it completely self contained.

@CAMOBAP
Copy link
Author

CAMOBAP commented Nov 30, 2019

Hi @ppetraki

Sorry for log silence

To build the fat APK you need to add this: ...

This not true, android building FAT APK out of the box, moreover split section used to build separate APK for each ABI, according to official doe https://developer.android.com/studio/build/configure-apk-splits:

universalApk
If true, Gradle generates a universal APK in addition to per-ABI APKs. A universal APK contains code and resources for all ABIs in a single APK. The default value is false. Note that this option is only available in the splits.abi block. When building multiple APKs based on screen density, Gradle always generates a universal APK that contains code and resources for all screen densities.

If flavors don't matter for the NDK build....

Flavors have no relationships with NDK, most close equivalent in CMake-word is a target

All that has to change in the plugin is ...

You can achieve the same by modifying the 'user' side only, no need to touch plugin for this

conan {
    conanfile = 'src/main/cpp/conanfile.txt'
    profile = 'android-${abi}'
    outputDirPath = '${projectDir}/conan_build/${buildType}/${abi}'
}

So according what I found from your comment there is no reason to modify the plugin by itself,

@ppetraki correct me if I'm wrong

@janseeger
Copy link

Would it be possible to release this as proper Gradle Plugin?

@CAMOBAP
Copy link
Author

CAMOBAP commented Mar 25, 2024

@janseeger probably not (at least in near future)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment