Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create an aar with all flutter libraries and dependencies inside

I need to create an aar with all the libraries of my flutter project inside, I created a flutter module and now I've to create an sdk in android to be embedded in a client app for that it would be nice to have a single aar file. I tried Mobbeel fat AAR Gradle plugin but with no avail. I know I can create a maven repository but that is not the solution I'm looking for right now. enter image description here

my project build.gradle

buildscript {
    repositories {
        maven { url "https://plugins.gradle.org/m2/" }
        google()
        jcenter()

    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath "com.mobbeel.plugin:fat-aar:2.0.1"
    }
}




allprojects {
    repositories {
        google()
        jcenter()
    }
}

and the app build.graddle

def flutterPluginVersion = 'managed'

apply plugin: 'com.android.library'
apply plugin: "com.mobbeel.plugin"

android {
    compileSdkVersion 28

    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }

    defaultConfig {
        minSdkVersion 21
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

buildDir = new File(rootProject.projectDir, "../build/host")



dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    api (project(':flutter'))


    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'androidx.annotation:annotation:1.1.0'
    implementation 'androidx.lifecycle:lifecycle-common:2.0.0'
}

aarPlugin {
    includeAllInnerDependencies false
    packagesToInclude = ["mobbeel"]
}

EDIT: I found a solution, but I'm not an android developer so a made some changes to the mobbeel plugin and add it to the build.gradle. After that I was able to add all the libraries to my aar by doing api project(":vibrate")

String archiveAarName

project.afterEvaluate {
    project.android.libraryVariants.all { variant ->

            variant.outputs.all {
                archiveAarName = outputFileName
            }

            print "afterEvaluate\n"

            def copyTask = createBundleDependenciesTask(variant)

            String rsDirPath = "${copyTask.temporaryDir.path}/rs/"
            String rsCompiledDirPath = "${copyTask.temporaryDir.path}/rs-compiled/"
            String sourceAarPath = "${copyTask.temporaryDir.path}/${variant.name}/"


            String taskNameCompileRs = "SDKcompileRs${variant.name.capitalize()}"
            String taskNameRsJa = "CreateRsJar${variant.name.capitalize()}"
            String taskNameCreateZip = "createZip${variant.name.capitalize()}"

            def compileRsTask = R2ClassTask(variant, rsDirPath, rsCompiledDirPath, taskNameCompileRs)
            def rsJarTask = bundleRJarTask(variant, rsCompiledDirPath, sourceAarPath, taskNameRsJa)
            def aarTask = bundleFinalAAR(variant, sourceAarPath, "finalname", taskNameCreateZip)


            def assembleTask = project.tasks.findByPath("assemble${variant.name.capitalize()}")

            assembleBundleDependenciesTask(variant).finalizedBy(assembleTask)
            assembleTask.finalizedBy(copyTask)
            copyTask.finalizedBy(compileRsTask)
            compileRsTask.finalizedBy(rsJarTask)
            rsJarTask.finalizedBy(aarTask)
        }
    }


Task assembleBundleDependenciesTask(def variant) {
    println "assembleBundleDependenciesTask -> ${variant.name}"

    return project.getTasks().create("hello_${variant}", {
        project.configurations.api.getDependencies().each { dependency ->

            if (dependency instanceof ProjectDependency) {

                Project dependencyProject = project.parent.findProject(dependency.name)

                String dependencyPath = "${dependencyProject.buildDir}"
                println "dependencyPath -> ${dependencyPath}"


                String variantName = "${variant.name}"

                def assembleTask = project.tasks.findByPath(":${dependency.name}:assemble${variant.name.capitalize()}")

                assembleTask.finalizedBy(copyTo( "${dependencyPath}/outputs/aar", variantName, dependency.name))
            }

            println ''
        }
    })

}

Task copyTo(String fromz, String variant, String dependency) {
    println "copyTo fromz -> $fromz "
    return project.task(type: Copy, "copyFile$dependency$variant") {
        from fromz
        into project.projectDir.path + "/build/outputs/aar/"
        include('*.aar')
        rename { String fileName ->
            fileName = "${dependency}-${variant}.aar"
        }
    }

}

Task createBundleDependenciesTask(def variant) {
    println "createBundleDependenciesTask -> ${variant.name}"

    String taskName = "copy${variant.name.capitalize()}Dependencies"
    return project.getTasks().create(taskName, CopyDependenciesTask.class, {
        it.includeInnerDependencies = true
        it.dependencies = project.configurations.api.getDependencies()
        it.variantName = variant.name
        it.gradleVersion = "3.2.1"
        it.buildAARDir = project.projectDir.path + "/build/outputs/aar/"
    })
}

Task R2ClassTask(def variant, String sourceDir, String destinationDir, String taskName) {
    print "R2ClassTask sourceDir -> $sourceDir to destDir -> $destinationDir"
    project.mkdir(destinationDir)

    def classpath

    classpath = project.files(project.projectDir.path +
            "/build/intermediates/javac/${variant.name}/compile${variant.name.capitalize()}JavaWithJavac/classes")



    return project.getTasks().create(taskName, JavaCompile.class, {
        it.source = sourceDir
        it.sourceCompatibility = project.android.compileOptions.sourceCompatibility
        it.targetCompatibility = project.android.compileOptions.targetCompatibility
        it.classpath = classpath
        it.destinationDir project.file(destinationDir)
    })
}

Task bundleRJarTask(def variant, String fromDir, String aarPath, String taskName) {
    print "bundleRJarTask\n"

    return project.getTasks().create(taskName, Jar.class, {
        it.from fromDir
        it.archiveName = "r-classes.jar"
        it.destinationDir project.file("${aarPath}/libs")
    })
}

Task bundleFinalAAR(def variant, String fromPath, name, String taskName) {
    print "bundleFinalAAR -> from ${fromPath} to > " + project.file(project.projectDir.path + "/build/outputs/aar/") + "\n"

    return project.getTasks().create(taskName, Zip.class, {
        it.from fromPath
        it.include "**"
        it.archiveName = "${name}-${variant.name}.aar"
        it.destinationDir(project.file(project.projectDir.path + "/build/outputs/aar/"))
    })
}

import groovy.xml.XmlUtil

class CopyDependenciesTask extends DefaultTask {

    Boolean includeInnerDependencies
    DependencySet dependencies
    String variantName
    String gradleVersion
    String[] packagesToInclude = [""]
    String buildAARDir

    @TaskAction
    def executeTask() {
        if (temporaryDir.exists()) {
            temporaryDir.deleteDir()
        }
        temporaryDir.mkdir()

        copyProjectBundles()
        analyzeDependencies()
    }


    def copyProjectBundles() {
        println "copyProjectBundles"

        if (gradleVersion.contains("3.2")) { // Version 3.4.x
            println "packaged-classes -> ${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/packaged-classes/"
            project.copy {
                from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/packaged-classes/"
                include "${variantName}/**"
                into temporaryDir.path
            }


            project.copy {
                from("${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/res/symbol-table-with-package/${variantName}") {
                    include "package-aware-r.txt"
                    rename '(.*)', 'R.txt'
                }

                from("${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/aapt_friendly_merged_manifests/" +
                        "${variantName}/process${variantName.capitalize()}Manifest/aapt/") {
                    include "AndroidManifest.xml"
                }

                into "${temporaryDir.path}/${variantName}"
            }

            println " check this -> ${temporaryDir.path}/${variantName}/R.txt"

            processRsAwareFile(new File("${temporaryDir.path}/${variantName}/R.txt"))

            project.copy {
                from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/packaged_res/${variantName}"
                include "**"
                into "${temporaryDir.path}/${variantName}/res"
            }

            project.copy {
                from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/library_assets/${variantName}/packageDebugAssets/out/"
                include "**"
                into "${temporaryDir.path}/${variantName}/assets"
            }
        }  else { // Version 3.0.x
            project.copy {
                from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/bundles/"
                from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/manifests/full/"
                include "${variantName}/**"
                exclude "**/output.json"
                into temporaryDir.path
            }
        }
    }

    def analyzeDependencies() {
        print "analyzeDependencies\n"
        dependencies.each { dependency ->
            def dependencyPath
            def archiveName
            print "dependency -> " + dependency
            if (dependency instanceof ProjectDependency) {
                print " instanceof -> ProjectDependency\n"
                String group = dependency.group
                Project dependencyProject

                dependencyProject = project.parent.findProject(dependency.name)


                println "dependencyProject -> ${dependencyProject}"


                if (dependencyProject.plugins.hasPlugin('java-library')) {
                    println "Internal java dependency detected -> " + dependency.name

                    archiveName = dependencyProject.jar.archiveName

                    dependencyPath = "${dependencyProject.buildDir}/libs/"
                } else {
                    println "Internal android dependency detected -> " + dependency.name

                    dependencyProject.android.libraryVariants.all {
                        if (it.name == variantName) {
                            it.outputs.all { archiveName = outputFileName }
                        }
                    }

                    dependencyPath = buildAARDir
                }

                processDependency(dependency, archiveName, dependencyPath)
            } else if (dependency instanceof ExternalModuleDependency) {
                println "External dependency detected -> " + dependency.group + ":" + dependency.name + ":" + dependency.version
                dependencyPath = project.gradle.getGradleUserHomeDir().path + "/caches/modules-2/files-2.1/"
                dependencyPath += dependency.group + "/" + dependency.name + "/" + dependency.version + "/"

                processDependency(dependency, archiveName, dependencyPath)
            } else {
                println "Not recognize type of dependency for " + dependency
                println()
            }
        }
    }

    /**
     * In this case dependency is outside from workspace, download from maven repository if file is
     * a jar directly move to lib/ folder and analyze pom file for detect another transitive dependency
     * @param dependency
     * @return
     */
    def processDependency(Dependency dependency, String archiveName, String dependencyPath) {
        println "processDependency -> ${archiveName} in ${dependencyPath}"
        project.fileTree(dependencyPath).getFiles().each { file ->
            println "processDependency file.name  -> ${file.name} "
            if (file.name.endsWith(".pom")) {
                println "POM: " + file.name
                processPomFile(file.path)
            } else {
                if (archiveName == null || file.name == archiveName) {
                    println "Artifact: " + file.name
                    if (file.name.endsWith(".aar")) {
                        processZipFile(file, dependency)
                    } else if (file.name.endsWith(".jar")) {
                        if (!file.name.contains("sources")) {
                            copyArtifactFrom(file.path)
                        } else {
                            println "   |--> Exclude for source jar"
                        }
                    }
                }
            }
        }
        println()
    }

    def processZipFile(File aarFile, Dependency dependency) {
        println "processZipFile"

        String tempDirPath = "${temporaryDir.path}/${dependency.name}_zip"

        println "tempDirPath -> ${tempDirPath}"

        project.copy {
            from project.zipTree(aarFile.path)
            include "**/*"
            into tempDirPath
        }

        File tempFolder = new File(tempDirPath)

        println "temporaryDir -> ${temporaryDir.path}/${variantName}/"

        project.copy {
            from "${tempFolder.path}"
            include "classes.jar"
            into "${temporaryDir.path}/${variantName}/libs"
            def jarName = getJarNameFromDependency(dependency)
            rename "classes.jar", jarName
        }

        project.copy {
            from "${tempFolder.path}/libs"
            include "**/*.jar"
            into "${temporaryDir.path}/${variantName}/libs"
        }

        project.copy {
            from "${tempFolder.path}/jni"
            include "**/*.so"
            into "${temporaryDir.path}/${variantName}/jni"
        }

        project.copy {
            from "${tempFolder.path}/assets"
            include "**/*"
            into "${temporaryDir.path}/${variantName}/assets"
        }

        project.copy {
            from "${tempFolder.path}/res"
            include "**/*"
            exclude "values/**"
            into "${temporaryDir.path}/${variantName}/res"
        }

        processValuesResource(tempFolder.path)
        processRsFile(tempFolder)

        println "tempFolder.deleteDir()"
        tempFolder.deleteDir()
    }

    def getJarNameFromDependency(Dependency dependency) {
        def jarName = ""
        if (null != dependency.group) {
            jarName += dependency.group.toLowerCase() + "-"
        }
        jarName += dependency.name.toLowerCase()
        if(null != dependency.version && !dependency.version.equalsIgnoreCase('unspecified')) {
            jarName += "-" + dependency.version
        }
        jarName += ".jar"

        return jarName
    }

    def processRsAwareFile(File resAwareFile) {
        println "processRsAwareFile"
        RandomAccessFile raf = new RandomAccessFile(resAwareFile, "rw")

        long writePosition = raf.getFilePointer()
        raf.readLine() // Move pointer to second line of file
        long readPosition = raf.getFilePointer()

        byte[] buffer = new byte[1024]
        int bytesInBuffer

        while (-1 != (bytesInBuffer = raf.read(buffer))) {

            raf.seek(writePosition)

            raf.write(buffer, 0, bytesInBuffer)
            readPosition += bytesInBuffer
            writePosition += bytesInBuffer

            raf.seek(readPosition)
        }
        raf.setLength(writePosition)

        raf.seek(0)

        if (gradleVersion.contains("3.2")) {
            String filePath = "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/symbols/${variantName}/R.txt"
            Scanner resourcesOriginal = new Scanner(new File(filePath))

            raf.seek(0) // Move pointer to first line

            String line
            int offset = 0
            while (resourcesOriginal.hasNextLine()) {
                boolean match = false
                line = resourcesOriginal.nextLine()
                println line

                line += "\n"

                byte[] data = line.getBytes()

                raf.seek(offset)
                raf.write(data, 0, data.length)
                offset += data.length

                raf.seek(offset + 1)

            }
        }

        raf.close()
    }

    def processRsFile(File tempFolder) {
        println "processRsFile"

        def mainManifestFile = project.android.sourceSets.main.manifest.srcFile
        def libPackageName = ""

        if (mainManifestFile.exists()) {
            println "processRsFile -> mainManifestFile.exists()"
            libPackageName = new XmlParser().parse(mainManifestFile).@package
        }

        def manifestFile = new File("$tempFolder/AndroidManifest.xml")
        if (manifestFile.exists()) {
            println "processRsFile -> manifestFile.exists()"
            def aarManifest = new XmlParser().parse(manifestFile)
            def aarPackageName = aarManifest.@package

            String packagePath = aarPackageName.replace('.', '/')

            // Generate the R.java file and map to current project's R.java
            // This will recreate the class file
            def rTxt = new File("$tempFolder/R.txt")
            def rMap = new ConfigObject()

            if (rTxt.exists()) {
                println "processRsFile -> rTxt.exists()"
                rTxt.eachLine { line ->
                    //noinspection GroovyUnusedAssignment
                    def (type, subclass, name, value) = line.tokenize(' ')
                    rMap[subclass].putAt(name, type)
                }
            }

            def sb = "package $aarPackageName;" << '\n' << '\n'
            sb << 'public final class R {' << '\n'

            rMap.each { subclass, values ->
                sb << "  public static final class $subclass {" << '\n'
                values.each { name, type ->
                    sb << "    public static $type $name = com.company.native_sdk.R.${subclass}.${name};" << '\n'
                }
                sb << "    }" << '\n'
            }

            sb << '}' << '\n'

            new File("${temporaryDir.path}/rs/$packagePath").mkdirs()
            FileOutputStream outputStream = new FileOutputStream("${temporaryDir.path}/rs/$packagePath/R.java")
            println "R file path -> ${temporaryDir.path}/rs/$packagePath/R.java"
            outputStream.write(sb.toString().getBytes())
            outputStream.close()
        }
    }

    def processValuesResource(String tempFolder) {
        println "processValuesResource"

        File valuesSourceFile = new File("${tempFolder}/res/values/values.xml")
        File valuesDestFile = new File("${temporaryDir.path}/${variantName}/res/values/values.xml")

        if (valuesSourceFile.exists()) {
            println "processValuesResource -> valuesSourceFile.exists"
            if (!valuesDestFile.exists()) {
                println "processValuesResource -> !valuesDestFile.exists"
                project.copy {
                    from "${tempFolder}/res"
                    include "values/*"
                    into "${temporaryDir.path}/${variantName}/res"
                }
            } else {
                println "processValuesResource -> valuesDestFile.exists"
                def valuesSource = new XmlSlurper().parse(valuesSourceFile)
                def valuesDest = new XmlSlurper().parse(valuesDestFile)

                valuesSource.children().each {
                    valuesDest.appendNode(it)
                }

                FileOutputStream fileOutputStream = new FileOutputStream(valuesDestFile, false)
                byte[] myBytes = XmlUtil.serialize(valuesDest).getBytes("UTF-8")
                fileOutputStream.write(myBytes)
                fileOutputStream.close()
            }
        } else {
            println "processValuesResource -> !valuesSourceFile.exists"
        }
    }

    def copyArtifactFrom(String path) {
        project.copy {
            includeEmptyDirs false
            from path
            include "**/*.jar"
            into "${temporaryDir.path}/${variantName}/libs"
            rename '(.*)', '$1'.toLowerCase()
        }
    }

    def processPomFile(String pomPath) {
        def pom = new XmlSlurper().parse(new File(pomPath))
        pom.dependencies.children().each {
            def subJarLocation = project.gradle.getGradleUserHomeDir().path + "/caches/modules-2/files-2.1/"
            if (!it.scope.text().equals("test") && !it.scope.text().equals("provided")) {
                String version = it.version.text()
                if (version.startsWith("\${") && version.endsWith("}")) {
                    pom.properties.children().each {
                        if (version.contains(it.name())) {
                            version = it.text()
                        }
                    }
                }

                println "   |--> Inner dependency: " +  it.groupId.text() + ":" + it.artifactId.text() + ":" + version

                if (includeInnerDependencies || it.groupId.text() in packagesToInclude) {
                    subJarLocation += it.groupId.text() + "/" + it.artifactId.text() + "/" + version + "/"
                    project.fileTree(subJarLocation).getFiles().each { file ->
                        if (file.name.endsWith(".pom")) {
                            println "   /--> " + file.name
                            processPomFile(file.path)
                        } else {
                            if (!file.name.contains("sources") && !file.name.contains("javadoc")) {
                                copyArtifactFrom(file.path)
                            }
                        }
                    }
                } else {
                    println "        (Exclude inner dependency)"
                }
            }
        }
    }
}
like image 617
Manuel Peixoto Avatar asked Jun 19 '19 11:06

Manuel Peixoto


1 Answers

  1. Add Android native library into your project in Android Studio: File -> New -> New module -> Android Library.

  2. After that add this plugin https://github.com/kezong/fat-aar-android into the project and replace 'implementation' by 'embed' keyword. Then your project structure will look like: enter image description here

  3. In flutter_library directory run command flutter build aar -v. Note: flutter_library contains Flutter related files, e.g lib/, .android, .ios, pubspec.yaml, etc

  4. In root project directory run ./gradlew assemble

  5. aar will be located in library/build/outputs/aar

See my example: https://github.com/askarsyzdykov/native_flutter_lib

like image 57
Askar Syzdykov Avatar answered Sep 28 '22 02:09

Askar Syzdykov