Android Build.Gradle ์„ค๋ช… ๐ŸŒŸ

13593 ๋‹จ์–ด androidgroovybuildgradlejava
์•ˆ๋…•ํ•˜์„ธ์š” ์นœ๊ตฌ,

Gradle์€ Android ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฐœ๋ฐœ ๋‹จ๊ณ„๋ฅผ ์ž๋™ํ™”ํ•˜๋Š” ๋นŒ๋“œ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.

Gradle ์‹œ์Šคํ…œ์—์„œ Java ํ”Œ๋žซํผ์šฉ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋™์•ˆ ์šฐ๋ฆฌ๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•œ ์–ธ์–ด์ธ Groovy ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Groovy๋Š” Java๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ„๋‹จํ•œ ์–ธ์–ด์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ฐจ์ด์ ์„ ์„ค๋ช…ํ•œ github repo๋ฅผ ์‚ดํŽด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ทธ๋ฃจ๋น„๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?

Groovy๋Š” ๊ฐ„๊ฒฐํ•˜๊ณ  ๋‹จ์ˆœํ•˜๋ฉฐ ๋ฐฐ์šฐ๊ธฐ ์‰ฌ์šด ๊ตฌ๋ฌธ์„ ํ†ตํ•ด ๊ฐœ๋ฐœ์ž ์ƒ์‚ฐ์„ฑ์„ ๋†’์ด๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•˜๋Š” Java ํ”Œ๋žซํผ์šฉ ์ •์  ํƒ€์ดํ•‘ ๋ฐ ์ •์  ์ปดํŒŒ์ผ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ ๊ฐ•๋ ฅํ•œ ์ฃผ๋ฌธํ˜• ์œ ํ˜• ๋™์  ์–ธ์–ด์ž…๋‹ˆ๋‹ค.

Groovy์—์„œ ๋ฐฐ์šด ์ด ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „๋ฌธ์ ์ธ ๋ฐฉ์‹์œผ๋กœ Android Studio์—์„œ build.gradle์„ ๋นŒ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Build.gradle์„ ์‚ฌ์šฉํ•˜๋ฉด Android Studio๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ  ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ(๋””๋ฒ„๊ทธ , ent, test, beta, prod.. ) ํŒ€๊ณผ ํŽธ์•ˆํ•˜๊ฒŒ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Gradle(์•ฑ) ๋„๊ตฌ ๋นŒ๋“œ

์ผ๋ฐ˜์ ์ธ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

`ํ”Œ๋Ÿฌ๊ทธ์ธ {
id 'com.android.application'

}
java.util.regex.Pattern ๊ฐ€์ ธ์˜ค๊ธฐ
์•ˆ๋“œ๋กœ์ด๋“œ {
์ปดํŒŒ์ผ SDK 32
defaultConfig {
applicationId "com.hakki.uygulamaadi"
์ตœ์†Œ SDK 21
targetSdk 32
resConfigs "en", "tr"
...
}
}
๋นŒ๋“œ ์œ ํ˜• {}
์ปดํŒŒ์ผ ์˜ต์…˜ {}
์ข…์†์„ฑ {
๊ตฌํ˜„ 'androidx.appcompat:appcompat:1.4.1'
...
}`

์ด์ œ build.gradle ์„ค์ •์— ๋ฌด์—‡์ด ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

signingConfigs: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฒŒ์‹œํ•  ๋•Œ ์›ํ•˜๋Š” ํ‚ค๊ฐ€ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๋ฆด๋ฆฌ์Šค๊ฐ€ ๋ฒ„์ „์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.

buildTypes: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์ˆ˜์ •ํ•˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

buildConfigField: ๋นŒ๋“œ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์•ˆ ์ƒ์ˆ˜ ๊ฐ’์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
Build๊ฐ€ ์ˆ˜์‹ ๋˜๋ฉด BuildConfig.java ํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ํด๋ž˜์Šค์—์„œ ์ด ์ƒ์ˆ˜์— ์•ก์„ธ์Šคํ•˜๋ ค๋ฉด BuildConfig.INFO๋กœ ์•ก์„ธ์Šคํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฐธ๊ณ : ์•„์ง ๋นŒ๋“œ๋ฅผ ๋ฐ›์ง€ ๋ชปํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฅ˜๊ฐ€ ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋นจ๊ฐ„์ƒ‰์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

productFlavors: ํ•œ ์ง€๋ถ• ์•„๋ž˜ ์—ฌ๋Ÿฌ ํ”„๋กœ์ ํŠธ๋ฅผ ์ˆ˜์ง‘ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ "main"์œผ๋กœ ์ œ๊ณต๋˜๋ฉฐ build.gradle์— ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. FlavorDimensions๋Š” ๋ฏธ๋ฆฌ ์ •์˜๋ฉ๋‹ˆ๋‹ค.

manifestPlaceholders: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•„์ด์ฝ˜์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
resValue: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋”ฐ๋ผ ๋ฆฌ์†Œ์Šค ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์˜ˆ: ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์ด๋ฆ„์ด ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ์–ธ๊ธ‰๋˜๋ฉด(์ผ๋ฐ˜์ ์ธ ์Šคํ”Œ๋ž˜์‹œ ๋˜๋Š” ํ™œ๋™์ผ ์ˆ˜ ์žˆ์Œ) ์—ฌ๊ธฐ์— ๊ฐ’์„ ํ• ๋‹นํ•˜์—ฌ ์—ญ๋™์„ฑ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

variantFilter: ๋” ๋งŽ์€ buildType์„ ์ถ”๊ฐ€ํ• ์ˆ˜๋ก ๊ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋” ๋งŽ์€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ. ํ…Œ์ŠคํŠธ, ๋””๋ฒ„๊ทธ, ๋ฒ ํƒ€, ์—”ํŠธ, ๋ฆด๋ฆฌ์Šค์™€ ๊ฐ™์€ ํ”„๋กœ์„ธ์Šค๋ฅผ ํ•  ์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—๋Š” ํ”„๋กœ์„ธ์Šค๊ฐ€ ๊ทธ๋Ÿฐ ์‹์œผ๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๋ชฉ๋ก์„ ์ฑ„์šฐ์ง€ ์•Š๋„๋ก variantFilter๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ œ๊ฑฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.



๋‹ค๋ฅธ ์ค‘์š”ํ•œ ์‚ฌํ•ญ์„ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ:
ํ”„๋กœ์ ํŠธ์˜ ๋ชจ๋“  ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๋ชจ์œผ๋Š” ๊ฒƒ์€ ๋ณ€๊ฒฝ ๋ฐ ๊ด€๋ฆฌ ์ธก๋ฉด์—์„œ ํŽธ๋ฆฌํ•จ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ถ”๊ฐ€ํ•ด์•ผ ํ•  ๋ช‡ ๊ฐ€์ง€ ์‚ฌ์†Œํ•œ ๊ฐœ์„  ์‚ฌํ•ญ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
์ด๊ฒƒ๋“ค;
  • Build๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ํ”„๋กœ์ ํŠธ์˜ ๋ชจ๋“  ํด๋ž˜์Šค๊ฐ€ ๊ฐ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์˜ .apk์— ๋กœ๋“œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๊ณตํ†ต ํด๋”๋ฅผ ์ •์˜ํ•˜๊ณ  ๊ฐ .apk์—์„œ ๊ณตํ†ต ํด๋ž˜์Šค ๋ฐ ๋ฆฌ์†Œ์Šค ํŒŒ์ผ ์—…๋กœ๋“œ,
  • ๋ฆด๋ฆฌ์Šค(๋ผ์ด๋ธŒ) ๋ฒ„์ „๋ณ„ ์ž‘์—…๋งŒ ์ˆ˜ํ–‰,

  • ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์— ๋Œ€ํ•ด ๋‚ด๊ฐ€ ๊ฐœ๋ฐœํ•œ ์†”๋ฃจ์…˜๊ณผ ํ•จ๊ป˜ ์•„๋ž˜์˜ ์ƒ˜ํ”Œ build.gradle ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๊ทธ๋“ค(์•ฑ)์„ ๋ชจ๋‘ ์ง€๋ฐฐํ•˜๋Š” ํ•˜๋‚˜์˜ ์•ˆ๋“œ๋กœ์ด๋“œ ํ”„๋กœ์ ํŠธ.

    plugins {
        id 'com.android.application'
    }
    
    import java.util.regex.Matcher
    import java.util.regex.Pattern
    
    /*
    sonarqube {
        properties {
            property "sonar.projectName", "LargeProjectExample"
            property "sonar.projectKey", "LargeProjectExample"
            property "sonar.host.url", "http://localhost:9000"
            property "sonar.language", "java"
            property "sonar.login", "admin"
            property "sonar.password", "******"
        }
    }
    */
    
    android {
        useLibrary 'org.apache.http.legacy'
        compileSdk 32
    
        defaultConfig {
            applicationId "com.largeproject.example"
            minSdk 21
            targetSdk 32
            versionCode 1
            versionName "1.0"
            resConfigs "en", "tr"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"    
        }
    
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
        signingConfigs {
            release {
                keyAlias 'key0'
                keyPassword '******'
                storeFile file('27tkey.jks')
                storePassword '******'
            }
        }
    
        buildTypes {
            // Development Environments
            release {
                signingConfig signingConfigs.release
                // zipAlignEnabled true      //
                //shrinkResources true   //res
                minifyEnabled true    //java code
                // multiDexEnabled true
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
                debuggable false
            }
            debug {
                //applicationIdSuffix ".debug"
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
                debuggable true
            }
            dev {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
                debuggable true
                /* DEV ORTAMI */
                buildConfigField "String", "INFO", '"someinfodev"'
            }
            plat {
                /* PLATFORM ORTAMI */
                buildConfigField "String", "INFO", '"someinfoplat"'
    
            }
            prod {
                /* PROD ORTAMI */
                buildConfigField "String", "INFO", '"someinfoprod"'
    
            }
        }
    
        flavorDimensions "APP"
        productFlavors {
    
            largeProject {
                dimension "APP"
    
                // CHANGE
                String appName = "Large Project"
                String appVersionName = "4.1.55"
                Integer appVersionCode = 255
                String appIdSuffix = ".largeproject"
                // CHANGE
    
                manifestPlaceholders = [appName: appName, appIcon: "@drawable/ic_second"]
                resValue("string", "app_name", appName)
                versionName appVersionName
                versionCode appVersionCode
                applicationIdSuffix appIdSuffix
            }
    
            largeProject2 {
                dimension "APP"
    
                String appName = "Another Large Project"
                String appVersionName = "4.1.55"
                Integer appVersionCode = 255
                String appIdSuffix = ".largeProject2"
    
                manifestPlaceholders = [appName: appName, appIcon: "@drawable/ic_second"]
                resValue("string", "app_name", appName)
                versionName appVersionName
                versionCode appVersionCode
                applicationIdSuffix appIdSuffix
    
                buildConfigField 'String', 'merhabaa', '"selam"'
            }
    
        }
    
        // Ignore Operations. Note: "==" operator is better than ".contains".
        variantFilter { variant ->
            String app = variant.flavors*.name.get(0).toLowerCase()
            String buildType = variant.buildType.name.toLowerCase()
            // println("app : " + app + " buildType : " + buildType)
    
            if (app == "largeproject" && buildType == "ent") {
                variant.setIgnore(true)
            }
        }
    
        // Changing the Default Media Storage Location Groovy
        List<String> resBuildTypes = new ArrayList<String>()
        List<String> resBuildType  = new ArrayList<String>()
        resBuildTypes.add("src/main/res/common")
    
        productFlavors.all { flavor  ->
            String folder = "src/main/res/customers/" + flavor.name
            resBuildTypes.add(folder)
            resBuildType.add(flavor.name)
        }
    
        //println resBuildTypes // output: [src/main/res/common, src/main/res/customers/largeProject, src/main/res/customers/largeProject2]
        //println resBuildType  //output: [largeProject, largeProject2]
    
        sourceSets {
            main.res.srcDirs  = resBuildTypes
            // also you can change manifest file for every customer
            //main.manifest.srcFile = "src/main/res/"
    
            for (String var : resBuildType) {
                "$var" {
                    setRoot "src/main/res/" + var
                }
            }
        }
    
        buildFeatures {
            buildFeatures.dataBinding = true
            buildFeatures.viewBinding = true
        }
    }
    
    
    // Build task that allows us to get the received project request information.
    def projectName
    def projectBuildType
    task getCurrentFlavor() {
        Gradle gradle = getGradle()
        String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
    
        Pattern pattern
    
        // take buildTypes to String
        String buildTypes = ""
        android.buildTypes.all {
            type ->
                //println(type.name) Capitalize
                String output = type.name.substring(0, 1).toUpperCase() + type.name.substring(1)
                buildTypes += output + "|"
        }
        buildTypes = buildTypes.substring(0, buildTypes.length() - 1) //delete last | character
        //println buildTypes
    
        if (tskReqStr.contains("assemble"))
            pattern = Pattern.compile("assemble(\\w+)($buildTypes)") // Addition Required for Every BuildType Created. Dev|Ent|Tst,
        else
            pattern = Pattern.compile("generate(\\w+)($buildTypes)") // $buildTypes fulfills this request by automating.
    
        Matcher matcher = pattern.matcher(tskReqStr)
    
        if (matcher.find()) {
            //projectVariant = matcher.group().toLowerCase()
            projectName = matcher.group(1).toLowerCase() // Changing to 2 will return build type, 1 provides product flavor
            projectBuildType = matcher.group(2).toLowerCase() // Changing to 2 will return build type, 1 provides product flavor
    
        } else {
            println "NO MATCH FOUND"
        }
    }
    
    task buildVariantTasks(type: Copy) {
        dependsOn getCurrentFlavor
        println "flavor name is " + projectName
        println "build type is "  + projectBuildType
    
        if(projectName!=null && projectBuildType!=null) {
    
            // Separating Classes and Resourses
            // To identify location of Common (Main) Classes, simply change the part that says "common".
    
            String appID = android.defaultConfig.applicationId
            String appIdSlashes = "src/main/java/" + appID.replaceAll("\\.", "/") + "/"
            android.sourceSets.main {
                java.srcDirs = [appIdSlashes + "common", appIdSlashes + projectName]
                res.srcDirs  = ['src/main/res/common', 'src/main/res/'+ projectName]
                manifest.srcFile "src/main/res/customers/" + projectName + "/AndroidManifest.xml"
            }
    
            android.applicationVariants.all { variant ->
    
                // Output APK Name & appVer - Specify a Common BuildConfig to Add to Each Product
                String buildType = variant.buildType.name
                String flavorName = variant.getFlavorName()
                String buildTime = new Date().format("yyMMddHHmm", TimeZone.getTimeZone("Asia/Istanbul"))
    
                variant.outputs.all {
                    buildConfigField 'String', 'INFO', "\"${flavorName}.${buildType}\""
                    buildConfigField 'String', 'appVer', "\"${versionName}_${buildTime}\""
                    outputFileName = getFileName(flavorName, versionName, buildTime, buildType)
                }
    
                // Action By Product Specific Defined BuildConfigField, If Product Is Defined ...
                /*variant.productFlavors.each { flavor ->
                    if (variant.getFlavorName() == projectName) {
    
                        flavor.buildConfigFields.each { key, value ->
                            if(key == "INFO") {
                                println value.type
                                println value.name
                                println value.value
                            }
                        }
                    }
                }*/
    
                // Release Actions for Release -> Allatori is here.
                if(projectBuildType.contains("release") && flavorName == projectName) {
    
                    // You can add here compression tools...
    
    
                }
    
            }
    
    
        }
    }
    preBuild.dependsOn buildVariantTasks
    
    
    static String getFileName(flavorName, versionName, buildTime, buildType) {
        return flavorName + "_" + versionName + "_" + buildTime + "_" + buildType + ".apk"
    }
    
    // Example of defining version for dependencies
    ext {
        appCombat = '1.4.1'
        material = '1.5.0'
        constrain = '2.1.3'
        junit = '4.13.2'
    }
    
    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
    
        implementation 'androidx.appcompat:appcompat:' + appCombat
        implementation 'com.google.android.material:material:'+ material
        implementation 'androidx.constraintlayout:constraintlayout:'+constrain
        implementation 'androidx.appcompat:appcompat:1.4.1'
        implementation 'com.google.android.material:material:1.5.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
        testImplementation 'junit:junit:'+junit
        androidTestImplementation 'androidx.test.ext:junit:1.1.3'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    }
    


    ๋‹ค์Œ ๊ธฐ์‚ฌ์—์„œ๋Š” Kotlin์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค์—์„œ ์–ด๋–ค ๊ฐœ์„  ์‚ฌํ•ญ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

    ์›์ฒœ:
    Gradle
    What it this Groovy?
    Github Build.Gradle

    ์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ