1. 程式人生 > >Android 在Gradle中上傳AAR, APK至Maven/FTP的指令碼

Android 在Gradle中上傳AAR, APK至Maven/FTP的指令碼

Android Studio Gradle Maven 上傳AAR,APK,JavaDoc,Source

在Gradle中上傳Android AAR, APK的Maven指令碼

說明

用於在構建基於Gradle的Android Application/Library時,向APK/AAR中新增構建資訊,打包JavaDoc,JavaSource,
ProgruardMapping,並向Maven倉庫提交.
注:為提高開發效率,僅當構建過程滿足isExpectBuild()方法的判定條件時,上述過程才會執行.

使用方法

在build.gradle中新增
apply from: ‘maven.gradle’
在buildscript依賴中新增
classpath ‘com.netflix.nebula:gradle-info-plugin:3.7.1’

最終可能如下所示:

apply plugin: 'com.android.application'	//或者 apply plugin: 'com.android.library'
apply from: '../maven.gradle'           //Maven上傳以及VCS資訊生成.

buildscript {
    repositories {
        google()
        maven {
            url "http://192.168.108.60/nexus/content/groups/public/"
        }
        jcenter(
) } dependencies { classpath "com.android.tools.build:gradle:$androidGradleVersion" classpath 'com.netflix.nebula:gradle-info-plugin:3.7.1' classpath "com.house365.build:android-shade-plugin:$shadeVersion" // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files } }

引數配置

並在gradle.properties中配置下列引數

 #控制開關
 publish_is_upload_remote = false                       //true:提交到遠端倉庫,false:提交到${project.rootDir}/repo目錄下
 publish_is_build_javadoc = true                        //是否生成JavaDoc以及打包Source

 #版本相關設定
 publish_group_id=com.house365                             	//your group_id
 publish_artifact_id=365TaoFang                         		//your artifact_id
 publish_version=6.1.5-SNAPSHOT                         	//your version
 publish_version_code=615

 #倉庫相關設定
 release_repository_url=ftp://192.168.108.42/projects/
 snapshot_repository_url=ftp://192.168.108.42/projects/
 nexus_username=your_username
 nexus_password=your_password

實現指令碼

maven.gradle 指令碼內容如下,目前Android Gradle Plugin 3.1.4測試可用,如發現不相容,可留言說明。
Github地址:https://github.com/zawn

import java.lang.reflect.Field
import java.text.SimpleDateFormat

/**
 * 用於在構建基於Gradle的Android Application/Library時,向APK/AAR中新增構建資訊,打包JavaDoc,JavaSource,
 * ProgruardMapping,並向Maven倉庫提交.
 * 注:為提高開發效率,僅當構建過程滿足isExpectBuild()方法的判定條件時,上述過程才會執行.
 *
 * 使用方法,在build.gradle中新增
 *      apply from: 'maven.gradle'
 * 並在gradle.properties中配置下列引數

 #控制開關
 publish_is_upload_remote = false                       //true:提交到遠端倉庫,false:提交到${project.rootDir}/repo目錄下
 publish_is_build_javadoc = true                        //是否生成JavaDoc以及打包Source

 #版本相關設定
 publish_group_id=com.myapp                             //your group_id
 publish_artifact_id=365TaoFang                         //your artifact_id
 publish_version=6.1.5-SNAPSHOT                         //your version
 publish_version_code=615

 #倉庫相關設定
 release_repository_url=ftp://192.168.108.42/projects/
 snapshot_repository_url=ftp://192.168.108.42/projects/
 nexus_username=your_username
 nexus_password=your_password

 */

buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
    }
}

apply plugin: 'maven'
apply plugin: 'nebula.info'

configurations {
    deployerJars
}

dependencies {
    deployerJars 'org.apache.maven.wagon:wagon-ftp:2.2'
}

def groupId = project.publish_group_id
def artifactId = project.publish_artifact_id
def version = project.publish_version

def localReleaseDest = "${project.getRootDir()}/repo"

@groovy.transform.Field private final java.util.Date date = new Date()

def getReleaseRepositoryUrl() {
    return hasProperty('release_repository_url') ? release_repository_url
            : "http://192.168.108.60/nexus/content/repositories/releases/"
}

def getSnapshotRepositoryUrl() {
    return hasProperty('snapshot_repository_url') ? snapshot_repository_url
            : "http://192.168.108.60/nexus/content/repositories/snapshots/"
}

def getRepositoryUsername() {
    return hasProperty('nexus_username') ? nexus_username : ""
}

def getRepositoryPassword() {
    return hasProperty('nexus_password') ? nexus_password : ""
}

def isUploadRemoteMaven() {
    return hasProperty('publish_is_upload_remote') ? publish_is_upload_remote.toBoolean() : false
}

def isBuildJavaDoc() {
    return hasProperty('publish_is_build_javadoc') ? publish_is_build_javadoc.toBoolean() : false
}

def getAndroidGradlePluginVersion() {
    Class<?> clazz = Class.forName("com.android.builder.model.Version", false, project.getBuildscript().getClassLoader());
    Field field = clazz.getField("ANDROID_GRADLE_PLUGIN_VERSION");
    String androidGradlePluginVersion = (String) field.get(clazz);
    String[] strings = androidGradlePluginVersion.split("-");
    return strings[0];
}

clean.doFirst {
    delete "file://${localReleaseDest}"
}

uploadArchives
        {
            repositories.mavenDeployer {
                //新增FTP協議支援.
                addProtocolProviderJars configurations.deployerJars.files

                pom.groupId = groupId
                pom.artifactId = artifactId
                pom.version = version
                // Add other pom properties here if you want (developer details / licenses)

                repository(url: "file://${localReleaseDest}")
                if (isUploadRemoteMaven()) {
                    // 關閉上傳至私有Maven倉庫
                    repository(url: getReleaseRepositoryUrl()) {
                        authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
                    }
                    snapshotRepository(url: getSnapshotRepositoryUrl()) {
                        authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
                    }
                }
            }
        }

/**
 * 歸檔APK檔案.
 */
private String defArchiveApkTasks(variant) {
    def variantData = variant.variantData
    final configuration = variantData.variantConfiguration
    def archiveApkTaskName = "archive${variant.name.capitalize()}Apk"
    def writeManifestPropertiesTask = tasks.getByName("writeManifestProperties")

    task(archiveApkTaskName, dependsOn: writeManifestPropertiesTask) {
        group "archive"
        description = 'APK file archiving. '
        Map<String, ?> manifest = writeManifestPropertiesTask.getManifest()
        for (Map.Entry<String, ?> entry : manifest.entrySet()) {
            String fieldName = entry.getKey();
            if (entry != null && fieldName != null) {
                fieldName = fieldName.replace("-", "_");
                final boolean matches = fieldName.matches("^[a-zA-Z_\$][a-zA-Z_\$0-9]*\$");
                if (matches) {
                    String fieldValue = entry.getValue() != null ? entry.getValue().toString() : "";
                    fieldValue = fieldValue.replace("\\", "\\\\");
                    fieldValue.replace("\"", "\\\"");
                    configuration.addBuildConfigField("String", fieldName, "\"" + fieldValue + "\"");
                } else {
                    logger.log(LogLevel.WARN, "Lost BuildConfigField,illegal name :" + fieldName)
                }
            }
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        configuration.addBuildConfigField("String", "Build_Date", "\"" + sdf.format(date) + "\"");
        String revision = manifest.get("Change");
        variant.outputs.each { output ->
            ext.destFile = output.outputFile
            if (revision == null)
                revision = ""
            ext.destRevision = revision
        }
    }
    variantData.generateBuildConfigTask.dependsOn(archiveApkTaskName)
    return archiveApkTaskName
}

def defJavaDocAndJavaSourceTasks = { dependsOnTaskName, variant, variantPrefix ->
    task("generate${variant.name.capitalize()}Javadoc", type: Javadoc, dependsOn: dependsOnTaskName) {
        description "Generates Javadoc for $project.name."
        group "javadoc"
        source = variant.javaCompile.source
        // compatible android gradle plugin 3.1

        def taskName = variant.variantData.getScope().getTaskName("compile", "JavaWithJavac")
        def classFiles = project.tasks.findByName(taskName).getClasspath()
        options {
            failOnError false
            encoding "utf-8"
            charSet "utf-8"
            links "http://docs.oracle.com/javase/7/docs/api/"
            linksOffline "http://d.android.com/reference", "${android.sdkDirectory}/docs/reference"
        }
        // fix java 8 very strict.
        if (JavaVersion.current().isJava8Compatible()) {
            options.addStringOption('Xdoclint:none', '-quiet')
        }
//        exclude '**/BuildConfig.java'
        exclude '**/R.java'
    }
    if (variantPrefix != null) {
        variantPrefix = variantPrefix + "-"
    } else {
        variantPrefix = ""
    }
    task("jar${variant.name.capitalize()}Javadoc", type: Jar, dependsOn: "generate${variant.name.capitalize()}Javadoc") {
        group "archive"
        description "Jar Javadoc for $project.name."
        classifier = "${variantPrefix}javadoc"
        from tasks.getByName("generate${variant.name.capitalize()}Javadoc").destinationDir
    }

    task("jar${variant.name.capitalize()}Sources", type: Jar, dependsOn: dependsOnTaskName) {
        group "archive"
        description "Jar sources for $project.name."
        classifier = "${variantPrefix}sources"
        from variant.javaCompile.source
    }
}

def defZipProgruardMappingTask = { dependsOnTaskName, variant ->
    if (variant.buildType.minifyEnabled) {
        String variantPrefix = variant.buildType.name
        if (variant.flavorName != null
                && !"".equals(variant.flavorName))
            variantPrefix = variant.flavorName + "-" + variantPrefix
        task("zip${variant.name.capitalize()}ProguardMapping", type: Zip, dependsOn: dependsOnTaskName) {
            group "archive"
            description = 'Assembles a ZIP archive containing the Proguard files of $variant.name..'
            classifier "${variantPrefix}-proguard"
            destinationDir = new File("$project.buildDir/libs/") // 設定和Jar預設路徑一致.
            from "$project.buildDir/outputs/mapping/${variant.getDirName()}"
        }
    }
}

def defGenerateManifestTask = { dependsOnTaskName, variant ->
    final configuration = variant.variantData.variantConfiguration
    def writeManifestPropertiesTask = tasks.getByName("writeManifestProperties")
    def processJavaRes = tasks.getByName("process${configuration.fullName.capitalize()}JavaRes")

    def manifestOutputDir = project.file(new File(project.buildDir, "generated/resources/manifest"))
    def manifestOutput = new File(manifestOutputDir, variant.dirName)
    processJavaRes.from(manifestOutput)

    def generateManifestTask = task("generate${variant.name.capitalize()}ManifestProperties").doLast {
        Properties properties = new Properties() {
            @Override
            public synchronized Enumeration<Object> keys() {
                return Collections.enumeration(new TreeSet<Object>(super.keySet()));
            }
        };
        FileInputStream inputStream = new FileInputStream(writeManifestPropertiesTask.getPropertiesFile())
        properties.load(inputStream)
        inputStream.close()
        properties.put("X-Android-PackageName", configuration.applicationId + "")
        properties.put("X-Android-VersionName", configuration.getVersionName() + "")
        properties.put("X-Android-VersionCode", configuration.getVersionCode() + "")
        properties.put("X-Android-FullName", configuration.fullName)
        properties.put("X-Android-BuildType", variant.buildType.name)
        properties.put("X-Android-FlavorName", variant.flavorName)
        properties.put("X-Android-MinSdkVersion", configuration.minSdkVersion.getApiString())
        properties.put("X-Android-TargetSdkVersion", configuration.targetSdkVersion.getApiString())

        File outputFile
        if (variant.variantData.getClass().name.contains("ApplicationVariantData"))
            outputFile = new File("${manifestOutput}/META-INF/build-info.properties")
        else {
            outputFile = new File("${manifestOutput}/META-INF/${configuration.applicationId}.properties")
        }

        if (outputFile.exists()) {
            Properties oldProp = new Properties();
            FileInputStream fileInputStream = new FileInputStream(outputFile)
            oldProp.load(fileInputStream)
            oldProp.remove("Build-Date");
            final buildDate = properties.remove("Build-Date");
            if (!oldProp.equals(properties)) {
                properties.put("Build-Date", buildDate)
            }
            fileInputStream.close()
        } else {
            if (!outputFile.getParentFile().exists())
                outputFile.getParentFile().mkdirs()
            outputFile.createNewFile()
        }

        if (properties.containsKey("Build-Date")) {
            try {
                BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputFile))
                properties.store(bufferedWriter, "\nAutomatic generated.\n\[email protected] ZhangZhenli\n")
                bufferedWriter.flush()
                bufferedWriter.close()
            } catch (IOException e) {
                throw e
            }
        }
    }

    generateManifestTask.dependsOn writeManifestPropertiesTask
    processJavaRes.dependsOn generateManifestTask
    project.tasks.findByName("generate${variant.name.capitalize()}Resources").dependsOn generateManifestTask
}

/**
 * 針對Android Library Module 打包AAR,Jar,JavaDoc,JavaSource。
 *
 * 注意:不支援變種.
 */
if (android.getClass().name.contains("LibraryExtension")) {
    android.libraryVariants.all { variant ->
        final configuration = variant.variantData.variantConfiguration
        String dependsOnTaskName = "assemble${configuration.fullName.capitalize()}"
        boolean isBuild = isExpectBuild(configuration)
        if (!"".equals(variant.flavorName)) {
            throw new GradleException("Do not set flavors ,the current maven.gradle script does not support multiple flavors!")
        }
        if (isBuild || variant.buildType.name.equals("release")) {
            if (isBuildJavaDoc()) {
                String variantPrefix = null
                defJavaDocAndJavaSourceTasks(dependsOnTaskName, variant, variantPrefix)
            }

            defZipProgruardMappingTask(dependsOnTaskName, variant)
            defGenerateManifestTask(dependsOnTaskName, variant)
            /**
             * 打包當前程式碼為Jar檔案.
             * 你在使用該Jar檔案的時候可能會出錯,因為該Jar檔案不包含需要的R檔案,如果確需引用請確保你在類
             * 中沒有對R的引用,如果你的Android Library Module不包含任何資原始檔或沒有對其進行引用則可放
             * 心使用。
             */
            def jarClassesWithoutRTaskName = "jar${variant.name.capitalize()}ClassesWithoutR"
            task(jarClassesWithoutRTaskName, dependsOn: dependsOnTaskName) {
                group "archive"
                description = 'Assemble a JAR file, the same as the Jar file included with the AAR,does not include class R.'
                if (project.getPlugins().hasPlugin("com.android.library")) {
                    String leaf;
                    String variantName = configuration.fullName;
                    String stringVersion = getAndroidGradlePluginVersion();
                    if (stringVersion.matches('^2.3.(\\*|\\d+)$')) {
                        // version match
                        if (android.getPublishNonDefault() ||
                                !variantName.equals(android.getDefaultPublishConfig())) {
                            leaf = variantName;
                        } else {
                            leaf = "default";
                        }
                    } else {
                        leaf = variantName;
                    }
                    ext.destFile = project.file("${project.buildDir}/intermediates/bundles/${leaf}/classes.jar")
                    if (stringVersion.matches('^3.1.(\\*|\\d+)$')) {
                        ext.destFile = project.file("${project.buildDir}/intermediates/packaged-classes/${leaf}/classes.jar")
                    }

                } else {
                    ext.destFile = project.file("${project.buildDir}/intermediates/classes-proguard/${variant.getDirName()}/classes.jar")
                }
            }

            artifacts {
                archives file: tasks.getByName(jarClassesWithoutRTaskName).destFile, classifier: 'jar', builtBy: tasks.getByName(jarClassesWithoutRTaskName)
                if (variant.buildType.minifyEnabled) {
                    archives tasks.getByName("zip${variant.name.capitalize()}ProguardMapping")