diff --git a/platforms/android/aar-template/OpenCV/build.gradle.template b/platforms/android/aar-template/OpenCV/build.gradle.template new file mode 100644 index 0000000000..4f3a3846ec --- /dev/null +++ b/platforms/android/aar-template/OpenCV/build.gradle.template @@ -0,0 +1,88 @@ +plugins { + id 'com.android.library' + id 'maven-publish' +} + +android { + namespace 'org.opencv' + compileSdk ${COMPILE_SDK} + + defaultConfig { + minSdk ${MIN_SDK} + targetSdk ${TARGET_SDK} + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags "" + arguments "-DANDROID_STL=${LIB_TYPE}" + } + } + ndk { + abiFilters ${ABI_FILTERS} + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_${JAVA_VERSION} + targetCompatibility JavaVersion.VERSION_${JAVA_VERSION} + } + externalNativeBuild { + cmake { + path file('src/main/cpp/CMakeLists.txt') + } + } + buildFeatures { + aidl true + prefabPublishing true + buildConfig true + } + prefab { + ${LIB_NAME} { + headers "src/main/cpp/include" + } + } + sourceSets { + main { + java.srcDirs = ['src/main/java'] + //jniLibs.srcDirs = ['libs'] + aidl.srcDirs = ['src/main/java'] + } + } + + publishing { + singleVariant('release') { + withSourcesJar() + } + } +} + +publishing { + publications { + release(MavenPublication) { + groupId = 'org.opencv' + artifactId = '${PACKAGE_NAME}' + version = '${OPENCV_VERSION}' + artifact("opencv-release.aar") + +// afterEvaluate { +// from components.release +// } + } + } + repositories { + maven { + name = 'myrepo' + url = "${project.buildDir}/repo" + } + } +} + +dependencies { +} \ No newline at end of file diff --git a/platforms/android/aar-template/OpenCV/proguard-rules.pro b/platforms/android/aar-template/OpenCV/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/platforms/android/aar-template/OpenCV/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/platforms/android/aar-template/OpenCV/src/main/AndroidManifest.xml b/platforms/android/aar-template/OpenCV/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..cce937ee78 --- /dev/null +++ b/platforms/android/aar-template/OpenCV/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/platforms/android/aar-template/OpenCV/src/main/cpp/CMakeLists.txt.template b/platforms/android/aar-template/OpenCV/src/main/cpp/CMakeLists.txt.template new file mode 100644 index 0000000000..02ef035e0a --- /dev/null +++ b/platforms/android/aar-template/OpenCV/src/main/cpp/CMakeLists.txt.template @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.6) + +project("opencv") + +add_library(${LIB_NAME} ${LIB_TYPE} native-lib.cpp) diff --git a/platforms/android/aar-template/OpenCV/src/main/cpp/include/temp.h b/platforms/android/aar-template/OpenCV/src/main/cpp/include/temp.h new file mode 100644 index 0000000000..4974d8ca96 --- /dev/null +++ b/platforms/android/aar-template/OpenCV/src/main/cpp/include/temp.h @@ -0,0 +1 @@ +// This empty .h file is used for creating an AAR with empty C++ lib that will be replaced with OpenCV C++ lib \ No newline at end of file diff --git a/platforms/android/aar-template/OpenCV/src/main/cpp/native-lib.cpp b/platforms/android/aar-template/OpenCV/src/main/cpp/native-lib.cpp new file mode 100644 index 0000000000..73ac04eb7a --- /dev/null +++ b/platforms/android/aar-template/OpenCV/src/main/cpp/native-lib.cpp @@ -0,0 +1 @@ +// This empty .cpp file is used for creating an AAR with empty C++ lib that will be replaced with OpenCV C++ lib \ No newline at end of file diff --git a/platforms/android/aar-template/README.md b/platforms/android/aar-template/README.md new file mode 100644 index 0000000000..a851364dd4 --- /dev/null +++ b/platforms/android/aar-template/README.md @@ -0,0 +1,29 @@ +## Scripts for creating an AAR package and a local Maven repository with OpenCV libraries for Android + +### How to run the scripts +1. Set JAVA_HOME and ANDROID_HOME enviroment variables. For example: +``` +export JAVA_HOME=~/Android Studio/jbr +export ANDROID_HOME=~/Android/SDK +``` +2. Download OpenCV SDK for Android +3. Run build script for version with Java and a shared C++ library: +``` +python build_java_shared_aar.py "~/opencv-4.7.0-android-sdk/OpenCV-android-sdk" +``` +4. Run build script for version with static C++ libraries: +``` +python build_static_aar.py "~/opencv-4.7.0-android-sdk/OpenCV-android-sdk" +``` +The AAR libraries and the local Maven repository will be created in the **outputs** directory +### Technical details +The scripts consist of 5 steps: +1. Preparing Android AAR library project template +2. Adding Java code to the project. Adding C++ public headers for shared version to the project. +3. Compiling the project to build an AAR package +4. Adding C++ binary libraries to the AAR package. Adding C++ public headers for static version to the AAR package. +5. Creating Maven repository with the AAR package + +There are a few minor limitations: +1. Due to the AAR design the Java + shared C++ AAR package contains duplicates of C++ binary libraries, but the final user's Android application contains only one library instance. +2. The compile definitions from cmake configs are skipped, but it shouldn't affect the library because the script uses precompiled C++ binaries from SDK. diff --git a/platforms/android/aar-template/build.gradle b/platforms/android/aar-template/build.gradle new file mode 100644 index 0000000000..b7a22c372c --- /dev/null +++ b/platforms/android/aar-template/build.gradle @@ -0,0 +1,4 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.library' version '8.0.2' apply false +} \ No newline at end of file diff --git a/platforms/android/aar-template/gradle.properties b/platforms/android/aar-template/gradle.properties new file mode 100644 index 0000000000..3e927b11ef --- /dev/null +++ b/platforms/android/aar-template/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.jar b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..e708b1c023 Binary files /dev/null and b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.jar differ diff --git a/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..9e6a7833f8 --- /dev/null +++ b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jul 10 11:57:38 SGT 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/platforms/android/aar-template/gradlew b/platforms/android/aar-template/gradlew new file mode 100755 index 0000000000..4f906e0c81 --- /dev/null +++ b/platforms/android/aar-template/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/platforms/android/aar-template/gradlew.bat b/platforms/android/aar-template/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/platforms/android/aar-template/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/platforms/android/aar-template/settings.gradle b/platforms/android/aar-template/settings.gradle new file mode 100644 index 0000000000..fb1a50602c --- /dev/null +++ b/platforms/android/aar-template/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "OpenCV" +include ':OpenCV' diff --git a/platforms/android/build_java_shared_aar.py b/platforms/android/build_java_shared_aar.py new file mode 100755 index 0000000000..ea59bad839 --- /dev/null +++ b/platforms/android/build_java_shared_aar.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python + +import argparse +from os import path +import os +import re +import shutil +import string +import subprocess + + +COPY_FROM_SDK_TO_ANDROID_PROJECT = [ + ["sdk/native/jni/include", "OpenCV/src/main/cpp/include"], + ["sdk/java/src/org", "OpenCV/src/main/java/org"], + ["sdk/java/res", "OpenCV/src/main/res"] +] + +COPY_FROM_SDK_TO_APK = [ + ["sdk/native/libs//lib.so", "jni//lib.so"], + ["sdk/native/libs//lib.so", "prefab/modules//libs/android./lib.so"], +] + +ANDROID_PROJECT_TEMPLATE_DIR = path.join(path.dirname(__file__), "aar-template") +TEMP_DIR = "build_java_shared" +ANDROID_PROJECT_DIR = path.join(TEMP_DIR, "AndroidProject") +COMPILED_AAR_PATH_1 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/OpenCV-release.aar") # original package name +COMPILED_AAR_PATH_2 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/opencv-release.aar") # lower case package name +AAR_UNZIPPED_DIR = path.join(TEMP_DIR, "aar_unzipped") +FINAL_AAR_PATH_TEMPLATE = "outputs/opencv_java_shared_.aar" +FINAL_REPO_PATH = "outputs/maven_repo" +MAVEN_PACKAGE_NAME = "opencv" + +def fill_template(src_path, dst_path, args_dict): + with open(src_path, "r") as f: + template_text = f.read() + template = string.Template(template_text) + text = template.safe_substitute(args_dict) + with open(dst_path, "w") as f: + f.write(text) + +def get_opencv_version(opencv_sdk_path): + version_hpp_path = path.join(opencv_sdk_path, "sdk/native/jni/include/opencv2/core/version.hpp") + with open(version_hpp_path, "rt") as f: + data = f.read() + major = re.search(r'^#define\W+CV_VERSION_MAJOR\W+(\d+)$', data, re.MULTILINE).group(1) + minor = re.search(r'^#define\W+CV_VERSION_MINOR\W+(\d+)$', data, re.MULTILINE).group(1) + revision = re.search(r'^#define\W+CV_VERSION_REVISION\W+(\d+)$', data, re.MULTILINE).group(1) + return "%(major)s.%(minor)s.%(revision)s" % locals() + +def get_compiled_aar_path(path1, path2): + if path.exists(path1): + return path1 + elif path.exists(path2): + return path2 + else: + raise Exception("Can't find compiled AAR path in [" + path1 + ", " + path2 + "]") + +def cleanup(paths_to_remove): + exists = False + for p in paths_to_remove: + if path.exists(p): + exists = True + if path.isdir(p): + shutil.rmtree(p) + else: + os.remove(p) + print("Removed", p) + if not exists: + print("Nothing to remove") + +def main(args): + opencv_version = get_opencv_version(args.opencv_sdk_path) + abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs")) + lib_name = "opencv_java" + opencv_version.split(".")[0] + final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("", opencv_version) + + print("Removing data from previous runs...") + cleanup([TEMP_DIR, final_aar_path, path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME)]) + + print("Preparing Android project...") + # ANDROID_PROJECT_TEMPLATE_DIR contains an Android project template that creates AAR + shutil.copytree(ANDROID_PROJECT_TEMPLATE_DIR, ANDROID_PROJECT_DIR) + + # Configuring the Android project to Java + shared C++ lib version + shutil.rmtree(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/include")) + + fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle.template"), + path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle"), + {"LIB_NAME": lib_name, + "LIB_TYPE": "c++_shared", + "PACKAGE_NAME": MAVEN_PACKAGE_NAME, + "OPENCV_VERSION": opencv_version, + "COMPILE_SDK": args.android_compile_sdk, + "MIN_SDK": args.android_min_sdk, + "TARGET_SDK": args.android_target_sdk, + "ABI_FILTERS": ", ".join(['"' + x + '"' for x in abis]), + "JAVA_VERSION": args.java_version, + }) + fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt.template"), + path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt"), + {"LIB_NAME": lib_name, "LIB_TYPE": "SHARED"}) + + # Copying Java code and C++ public headers from SDK to the Android project + for src, dst in COPY_FROM_SDK_TO_ANDROID_PROJECT: + shutil.copytree(path.join(args.opencv_sdk_path, src), + path.join(ANDROID_PROJECT_DIR, dst)) + + print("Running gradle assembleRelease...") + # Running gradle to build the Android project + subprocess.run(["./gradlew", "assembleRelease"], + shell=False, + cwd=ANDROID_PROJECT_DIR, + check=True) + + print("Adding libs to AAR...") + # The created AAR package doesn't contain C++ shared libs. + # We need to add them manually. + # AAR package is just a zip archive. + complied_aar_path = get_compiled_aar_path(COMPILED_AAR_PATH_1, COMPILED_AAR_PATH_2) # two possible paths + shutil.unpack_archive(complied_aar_path, AAR_UNZIPPED_DIR, "zip") + + for abi in abis: + for src, dst in COPY_FROM_SDK_TO_APK: + src = src.replace("", abi).replace("", lib_name) + dst = dst.replace("", abi).replace("", lib_name) + shutil.copy(path.join(args.opencv_sdk_path, src), + path.join(AAR_UNZIPPED_DIR, dst)) + + # Creating final AAR zip archive + os.makedirs("outputs", exist_ok=True) + shutil.make_archive(final_aar_path, "zip", AAR_UNZIPPED_DIR, ".") + os.rename(final_aar_path + ".zip", final_aar_path) + + print("Creating local maven repo...") + + shutil.copy(final_aar_path, path.join(ANDROID_PROJECT_DIR, "OpenCV/opencv-release.aar")) + subprocess.run(["./gradlew", "publishReleasePublicationToMyrepoRepository"], + shell=False, + cwd=ANDROID_PROJECT_DIR, + check=True) + + os.makedirs(path.join(FINAL_REPO_PATH, "org/opencv"), exist_ok=True) + shutil.move(path.join(ANDROID_PROJECT_DIR, "OpenCV/build/repo/org/opencv", MAVEN_PACKAGE_NAME), + path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Builds AAR with Java and shared C++ libs from OpenCV SDK") + parser.add_argument('opencv_sdk_path') + parser.add_argument('--android_compile_sdk', default="26") + parser.add_argument('--android_min_sdk', default="21") + parser.add_argument('--android_target_sdk', default="26") + parser.add_argument('--java_version', default="1_8") + args = parser.parse_args() + + main(args) diff --git a/platforms/android/build_static_aar.py b/platforms/android/build_static_aar.py new file mode 100755 index 0000000000..df6d55a6fd --- /dev/null +++ b/platforms/android/build_static_aar.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python + +import argparse +import json +from os import path +import os +import shutil +import subprocess + +from build_java_shared_aar import cleanup, fill_template, get_compiled_aar_path, get_opencv_version + + +ANDROID_PROJECT_TEMPLATE_DIR = path.join(path.dirname(__file__), "aar-template") +TEMP_DIR = "build_static" +ANDROID_PROJECT_DIR = path.join(TEMP_DIR, "AndroidProject") +COMPILED_AAR_PATH_1 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/OpenCV-release.aar") # original package name +COMPILED_AAR_PATH_2 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/opencv-release.aar") # lower case package name +AAR_UNZIPPED_DIR = path.join(TEMP_DIR, "aar_unzipped") +FINAL_AAR_PATH_TEMPLATE = "outputs/opencv_static_.aar" +FINAL_REPO_PATH = "outputs/maven_repo" +MAVEN_PACKAGE_NAME = "opencv-static" + + +def get_list_of_opencv_libs(sdk_dir): + files = os.listdir(path.join(sdk_dir, "sdk/native/staticlibs/arm64-v8a")) + libs = [f[3:-2] for f in files if f[:3] == "lib" and f[-2:] == ".a"] + return libs + +def get_list_of_3rdparty_libs(sdk_dir, abis): + libs = [] + for abi in abis: + files = os.listdir(path.join(sdk_dir, "sdk/native/3rdparty/libs/" + abi)) + cur_libs = [f[3:-2] for f in files if f[:3] == "lib" and f[-2:] == ".a"] + for lib in cur_libs: + if lib not in libs: + libs.append(lib) + return libs + +def add_printing_linked_libs(sdk_dir, opencv_libs): + """ + Modifies CMakeLists.txt file in Android project, so it prints linked libraries for each OpenCV library" + """ + sdk_jni_dir = sdk_dir + "/sdk/native/jni" + with open(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt"), "a") as f: + f.write('\nset(OpenCV_DIR "' + sdk_jni_dir + '")\n') + f.write('find_package(OpenCV REQUIRED)\n') + for lib_name in opencv_libs: + output_filename_prefix = "linkedlibs." + lib_name + "." + f.write('get_target_property(OUT "' + lib_name + '" INTERFACE_LINK_LIBRARIES)\n') + f.write('file(WRITE "' + output_filename_prefix + '${ANDROID_ABI}.txt" "${OUT}")\n') + +def read_linked_libs(lib_name, abis): + """ + Reads linked libs for each OpenCV library from files, that was generated by gradle. See add_printing_linked_libs() + """ + deps_lists = [] + for abi in abis: + with open(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp", f"linkedlibs.{lib_name}.{abi}.txt")) as f: + text = f.read() + linked_libs = text.split(";") + linked_libs = [x.replace("$", "") for x in linked_libs] + deps_lists.append(linked_libs) + + return merge_dependencies_lists(deps_lists) + +def merge_dependencies_lists(deps_lists): + """ + One library may have different dependencies for different ABIS. + We need to merge them into one list with all the dependencies preserving the order. + """ + result = [] + for d_list in deps_lists: + for i in range(len(d_list)): + if d_list[i] not in result: + if i == 0: + result.append(d_list[i]) + else: + index = result.index(d_list[i-1]) + result = result[:index + 1] + [d_list[i]] + result[index + 1:] + + return result + +def convert_deps_list_to_prefab(linked_libs, opencv_libs, external_libs): + """ + Converting list of dependencies into prefab format. + """ + prefab_linked_libs = [] + for lib in linked_libs: + if (lib in opencv_libs) or (lib in external_libs): + prefab_linked_libs.append(":" + lib) + elif (lib[:3] == "lib" and lib[3:] in external_libs): + prefab_linked_libs.append(":" + lib[3:]) + elif lib == "ocv.3rdparty.android_mediandk": + prefab_linked_libs += ["-landroid", "-llog", "-lmediandk"] + print("Warning: manualy handled ocv.3rdparty.android_mediandk dependency") + elif lib == "ocv.3rdparty.flatbuffers": + print("Warning: manualy handled ocv.3rdparty.flatbuffers dependency") + elif lib.startswith("ocv.3rdparty"): + raise Exception("Unknown lib " + lib) + else: + prefab_linked_libs.append("-l" + lib) + return prefab_linked_libs + +def main(args): + opencv_version = get_opencv_version(args.opencv_sdk_path) + abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs")) + final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("", opencv_version) + sdk_dir = args.opencv_sdk_path + + print("Removing data from previous runs...") + cleanup([TEMP_DIR, final_aar_path, path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME)]) + + print("Preparing Android project...") + # ANDROID_PROJECT_TEMPLATE_DIR contains an Android project template that creates AAR + shutil.copytree(ANDROID_PROJECT_TEMPLATE_DIR, ANDROID_PROJECT_DIR) + + # Configuring the Android project to static C++ libs version + fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle.template"), + path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle"), + {"LIB_NAME": "templib", + "LIB_TYPE": "c++_static", + "PACKAGE_NAME": MAVEN_PACKAGE_NAME, + "OPENCV_VERSION": opencv_version, + "COMPILE_SDK": args.android_compile_sdk, + "MIN_SDK": args.android_min_sdk, + "TARGET_SDK": args.android_target_sdk, + "ABI_FILTERS": ", ".join(['"' + x + '"' for x in abis]), + "JAVA_VERSION": args.java_version, + }) + fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt.template"), + path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt"), + {"LIB_NAME": "templib", "LIB_TYPE": "STATIC"}) + + opencv_libs = get_list_of_opencv_libs(sdk_dir) + external_libs = get_list_of_3rdparty_libs(sdk_dir, abis) + + add_printing_linked_libs(sdk_dir, opencv_libs) + + print("Running gradle assembleRelease...") + # Running gradle to build the Android project + subprocess.run(["./gradlew", "assembleRelease"], + shell=False, + cwd=ANDROID_PROJECT_DIR, + check=True) + + # The created AAR package contains only one empty libtemplib.a library. + # We need to add OpenCV libraries manually. + # AAR package is just a zip archive + complied_aar_path = get_compiled_aar_path(COMPILED_AAR_PATH_1, COMPILED_AAR_PATH_2) # two possible paths + shutil.unpack_archive(complied_aar_path, AAR_UNZIPPED_DIR, "zip") + + print("Adding libs to AAR...") + + # Copying 3rdparty libs from SDK into the AAR + for lib in external_libs: + for abi in abis: + os.makedirs(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi)) + if path.exists(path.join(sdk_dir, "sdk/native/3rdparty/libs/" + abi, "lib" + lib + ".a")): + shutil.copy(path.join(sdk_dir, "sdk/native/3rdparty/libs/" + abi, "lib" + lib + ".a"), + path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi, "lib" + lib + ".a")) + else: + # One OpenCV library may have different dependency lists for different ABIs, but we can write only one + # full dependency list for all ABIs. So we just add empty .a library if this ABI doesn't have this dependency. + shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/libs/android." + abi, "libtemplib.a"), + path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi, "lib" + lib + ".a")) + shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/libs/android." + abi + "/abi.json"), + path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi + "/abi.json")) + shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/module.json"), + path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/module.json")) + + # Copying OpenV libs from SDK into the AAR + for lib in opencv_libs: + for abi in abis: + os.makedirs(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi)) + shutil.copy(path.join(sdk_dir, "sdk/native/staticlibs/" + abi, "lib" + lib + ".a"), + path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi, "lib" + lib + ".a")) + shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/libs/android." + abi + "/abi.json"), + path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi + "/abi.json")) + os.makedirs(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/include/opencv2")) + shutil.copy(path.join(sdk_dir, "sdk/native/jni/include/opencv2/" + lib.replace("opencv_", "") + ".hpp"), + path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/include/opencv2/" + lib.replace("opencv_", "") + ".hpp")) + shutil.copytree(path.join(sdk_dir, "sdk/native/jni/include/opencv2/" + lib.replace("opencv_", "")), + path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/include/opencv2/" + lib.replace("opencv_", ""))) + + # Adding dependencies list + module_json_text = { + "export_libraries": convert_deps_list_to_prefab(read_linked_libs(lib, abis), opencv_libs, external_libs), + "android": {}, + } + with open(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/module.json"), "w") as f: + json.dump(module_json_text, f) + + for h_file in ("cvconfig.h", "opencv.hpp", "opencv_modules.hpp"): + shutil.copy(path.join(sdk_dir, "sdk/native/jni/include/opencv2/" + h_file), + path.join(AAR_UNZIPPED_DIR, "prefab/modules/opencv_core/include/opencv2/" + h_file)) + + + shutil.rmtree(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib")) + + # Creating final AAR zip archive + os.makedirs("outputs", exist_ok=True) + shutil.make_archive(final_aar_path, "zip", AAR_UNZIPPED_DIR, ".") + os.rename(final_aar_path + ".zip", final_aar_path) + + print("Creating local maven repo...") + + shutil.copy(final_aar_path, path.join(ANDROID_PROJECT_DIR, "OpenCV/opencv-release.aar")) + + subprocess.run(["./gradlew", "publishReleasePublicationToMyrepoRepository"], + shell=False, + cwd=ANDROID_PROJECT_DIR, + check=True) + + os.makedirs(path.join(FINAL_REPO_PATH, "org/opencv"), exist_ok=True) + shutil.move(path.join(ANDROID_PROJECT_DIR, "OpenCV/build/repo/org/opencv", MAVEN_PACKAGE_NAME), + path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME)) + print("Done") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Builds AAR with static C++ libs from OpenCV SDK") + parser.add_argument('opencv_sdk_path') + parser.add_argument('--android_compile_sdk', default="26") + parser.add_argument('--android_min_sdk', default="21") + parser.add_argument('--android_target_sdk', default="26") + parser.add_argument('--java_version', default="1_8") + args = parser.parse_args() + + main(args)