diff --git a/.circleci/config.yml b/.circleci/config.yml index b5270b3aa..f403f7f39 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,7 +21,7 @@ docker_env: - image: circleci/android:api-28-alpha environment: <<: *cache_version_keys - JAVA_OPTS: "-Xms512m -Xmx2048m" + JAVA_OPTS: "-Xmx1024m" GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError" -Dorg.gradle.daemon=false -Dorg.gradle.parallel=false' go_defaults: &go_defaults @@ -66,7 +66,7 @@ jobs: name: Assemble apk from App Bundle command: | use_debug_keystore - ./gradlew bundleDebug --offline + ./gradlew bundleStaging --offline create_universal_apk_from_aab.bash $(find frontend/android/build/outputs -name "*.aab" | head -1) - store_artifacts: path: frontend/android/build/outputs @@ -104,7 +104,7 @@ jobs: - restore_cache: *restore_gradle_cache - run: *download_all_dependencies - save_cache: *save_gradle_cache - - run: ./gradlew testDebugUnitTest lintDebug ktlint --continue --offline + - run: ./gradlew lintDebug testDebugUnitTest ktlint --continue --offline # we want to check lintRelease as well but RAM shortage... oh my... - run: name: Aggregate junit report files into one dir command: aggregate_junit_results diff --git a/.gitignore b/.gitignore index 69744615d..11553a423 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ build/ **/vendor/bundle/ **/xcuserdata/ + +proguard-merged-config.txt diff --git a/frontend/android/build.gradle b/frontend/android/build.gradle index 56e579df6..3da8696e6 100644 --- a/frontend/android/build.gradle +++ b/frontend/android/build.gradle @@ -18,6 +18,12 @@ android { storeFile file('debug.keystore') storePassword 'droidkaigi' } + release { + keyAlias System.getenv("RELEASE_KEY_ALIAS") + keyPassword System.getenv("RELEASE_KEY_PASSWORD") + storeFile file(System.getenv("RELEASE_STORE_FILE") ?: "debug.keystore") + storePassword System.getenv("RELEASE_PASSWORD") + } } defaultConfig { applicationId "io.github.droidkaigi.confsched2019" @@ -33,10 +39,33 @@ android { manifestPlaceholders = [isCi: String.valueOf(isCi)] } buildTypes { + staging { + zipAlignEnabled true + + postprocessing { + proguardFiles 'proguard-rules.pro' + obfuscate = true + // if we want to enable optimization, then codeShrinker must be proguard for now + // That may be a bug of r8 + optimizeCode = false + removeUnusedCode = true + removeUnusedResources = true + codeShrinker = 'r8' + } + } + release.initWith(staging) + staging { + debuggable true + signingConfig = signingConfigs.debug + matchingFallbacks = ['release'] + } release { - applicationIdSuffix "" - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + applicationIdSuffix null + signingConfig = signingConfigs.release + + postprocessing { + proguardFiles 'proguard-rules-production.pro' + } } } dataBinding { @@ -50,6 +79,9 @@ android { xmlOutput file("lint-results.xml") htmlOutput file("lint-results.html") + + checkReleaseBuilds !isCi + abortOnError !isCi } packagingOptions { exclude 'META-INF/*.kotlin_module' @@ -98,9 +130,10 @@ dependencies { implementation Dep.Stetho.stetho - releaseImplementation Dep.LeakCanary.leakCanary debugImplementation Dep.LeakCanary.leakCanary debugImplementation Dep.LeakCanary.leakCanaryFragment + stagingImplementation Dep.LeakCanary.leakCanaryNoOp + releaseImplementation Dep.LeakCanary.leakCanaryNoOp implementation Dep.Firebase.core implementation Dep.Firebase.auth @@ -123,8 +156,8 @@ dependencies { Dep.Hyperion.hyperionPlugins.forEach { debugImplementation it + stagingImplementation it } } - apply plugin: 'com.google.gms.google-services' diff --git a/frontend/android/proguard-rules-production.pro b/frontend/android/proguard-rules-production.pro new file mode 100644 index 000000000..da1573da2 --- /dev/null +++ b/frontend/android/proguard-rules-production.pro @@ -0,0 +1,31 @@ +# Remove LogCat Tree anyway +-assumenosideeffects class io.github.droidkaigi.confsched2019.App { + public *** enableLogCatLogging(); +} + +# Remove Android Log's methods +-assumenosideeffects class android.util.Log { + public static *** e(...); + public static *** d(...); + public static *** i(...); + public static *** v(...); + public static *** w(...); + public static *** wtf(...); +} + +# Remove log methods which CrashlyticsTree doesn't support +-assumenosideeffects class timber.log.Timber { + public static *** d(...); + public static *** i(...); + public static *** v(...); + public static *** w(...); + public static *** wtf(...); +} + +# Remove our log methods which are lower than ERROR +-assumenosideeffects class io.github.droidkaigi.confsched2019.timber.Timber { + *** info(...); + *** warn(...); + *** debug(...); + *** verbose(...); +} diff --git a/frontend/android/proguard-rules.pro b/frontend/android/proguard-rules.pro index f1b424510..d518643b2 100644 --- a/frontend/android/proguard-rules.pro +++ b/frontend/android/proguard-rules.pro @@ -5,17 +5,48 @@ # 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 +## Extra + +# Generate merge config list +-printconfiguration proguard-merged-config.txt + +## General + +# Do not remove annotations +-keepattributes *Annotation* +-keepattributes EnclosingMethod,Signature + +# Replace source file attributes by SourceFile to reduce the size +# report system can de-obfuscate them +-renamesourcefileattribute SourceFile +# To see readable stacktraces +-keepattributes SourceFile,LineNumberTable + +-dontwarn org.jetbrains.annotations.** + +## Kotlin Coroutine stuff + +# ServiceLoader-related stuff should be kept unless we use Dispatchers.Main +-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler + +# Most of volatile fields are updated with AFU and should not be mangled +-keepclassmembernames class kotlinx.** { + volatile ; +} + +# AFU stuff +-dontwarn kotlinx.atomicfu.** + +## OkHttp + +-dontwarn okhttp3.** +-dontwarn okio.** +-dontwarn javax.annotation.** +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +## Ktor + +-dontwarn com.typesafe.** +-dontwarn org.slf4j.** diff --git a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/App.kt b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/App.kt index daa15ce9f..cbefab775 100644 --- a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/App.kt +++ b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/App.kt @@ -19,6 +19,7 @@ import io.github.droidkaigi.confsched2019.di.createAppComponent import io.github.droidkaigi.confsched2019.ext.android.changedForever import io.github.droidkaigi.confsched2019.system.actioncreator.SystemActionCreator import io.github.droidkaigi.confsched2019.system.store.SystemStore +import timber.log.LogcatTree import timber.log.Timber import timber.log.Tree import timber.log.debug @@ -87,6 +88,11 @@ open class App : DaggerApplication() { open fun setupLogHandler() { Fabric.with(this, Crashlytics()) Timber.plant(CrashlyticsTree()) + enableLogCatLogging() + } + + fun enableLogCatLogging() { + Timber.plant(LogcatTree("droidkaigi")) } class CrashlyticsTree : Tree() { diff --git a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/DebugApp.kt b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/DebugApp.kt index a50a090b9..e375e00cd 100644 --- a/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/DebugApp.kt +++ b/frontend/android/src/main/java/io/github/droidkaigi/confsched2019/DebugApp.kt @@ -2,8 +2,6 @@ package io.github.droidkaigi.confsched2019 import com.facebook.stetho.Stetho import com.squareup.leakcanary.LeakCanary -import timber.log.LogcatTree -import timber.log.Timber class DebugApp : App() { override fun onCreate() { @@ -24,6 +22,6 @@ class DebugApp : App() { } override fun setupLogHandler() { - Timber.plant(LogcatTree("droidkaigi")) + enableLogCatLogging() } } diff --git a/frontendcomponent/androidcomponent/build.gradle b/frontendcomponent/androidcomponent/build.gradle index 9fb1e8451..ebe272eeb 100644 --- a/frontendcomponent/androidcomponent/build.gradle +++ b/frontendcomponent/androidcomponent/build.gradle @@ -6,6 +6,10 @@ apply plugin: 'androidx.navigation.safeargs' apply from: rootProject.file('gradle/android.gradle') +android.buildTypes.release { + consumerProguardFiles 'proguard-rules-navigation.pro' +} + dependencies { api project(":model") api project(":feature:notification") @@ -37,9 +41,14 @@ dependencies { androidTestImplementation Dep.Test.espressoCore } repositories { - if (!isCi) { maven { url "https://maven-central-asia.storage-download.googleapis.com/repos/central/data/" } } + if (!isCi) { + maven { + url "https://maven-central-asia.storage-download.googleapis.com/repos/central/data/" + } + } mavenCentral() } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { freeCompilerArgs = [ @@ -48,3 +57,38 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { ] } } + +task buildNavigationProguard << { + def navigationGraphXml = project.file("src/main/res/navigation/navigation.xml") + + // XmlParser didn't work so let me use the dirty hack + if (!navigationGraphXml.text.contains("xmlns:app=\"http://schemas.android.com/apk/res-auto\"")) { + throw new GradleScriptException("the namespace has been changed from app") + } + + def fqdns = [] + + navigationGraphXml.eachLine { + def line = it.trim() + + if (line.startsWith("android:name=\"")) { + def className = line.substring("android:name=\"".length(), line.lastIndexOf("\"")) + + if (className.contains(".")) { + fqdns << className + } + } else if (line.startsWith("app:argType=\"")) { + def className = line.substring("app:argType=\"".length(), line.lastIndexOf("\"")) + + if (className.contains(".")) { + fqdns << className + } + } + } + + project.file("proguard-rules-navigation.pro").write(fqdns.sort().collect { + "-keepnames class $it" + }.join("\n")) +} + +preBuild.dependsOn(buildNavigationProguard) diff --git a/frontendcomponent/androidcomponent/proguard-rules-navigation.pro b/frontendcomponent/androidcomponent/proguard-rules-navigation.pro new file mode 100644 index 000000000..427f13422 --- /dev/null +++ b/frontendcomponent/androidcomponent/proguard-rules-navigation.pro @@ -0,0 +1,17 @@ +-keepnames class com.google.android.gms.oss.licenses.OssLicensesMenuActivity +-keepnames class io.github.droidkaigi.confsched2019.about.ui.AboutFragment +-keepnames class io.github.droidkaigi.confsched2019.announcement.ui.AnnouncementFragment +-keepnames class io.github.droidkaigi.confsched2019.floormap.ui.FloorMapFragment +-keepnames class io.github.droidkaigi.confsched2019.model.SpeechSession +-keepnames class io.github.droidkaigi.confsched2019.session.ui.BottomSheetDaySessionsFragment +-keepnames class io.github.droidkaigi.confsched2019.session.ui.BottomSheetFavoriteSessionsFragment +-keepnames class io.github.droidkaigi.confsched2019.session.ui.SearchFragment +-keepnames class io.github.droidkaigi.confsched2019.session.ui.SessionDetailFragment +-keepnames class io.github.droidkaigi.confsched2019.session.ui.SessionPageFragment +-keepnames class io.github.droidkaigi.confsched2019.session.ui.SessionPagesFragment +-keepnames class io.github.droidkaigi.confsched2019.session.ui.SpeakerFragment +-keepnames class io.github.droidkaigi.confsched2019.session.ui.TabularFormSessionPagesFragment +-keepnames class io.github.droidkaigi.confsched2019.settings.ui.SettingsFragment +-keepnames class io.github.droidkaigi.confsched2019.sponsor.ui.SponsorFragment +-keepnames class io.github.droidkaigi.confsched2019.staff.ui.StaffSearchFragment +-keepnames class io.github.droidkaigi.confsched2019.survey.ui.SessionSurveyFragment \ No newline at end of file diff --git a/gradle/android.gradle b/gradle/android.gradle index 4e402eceb..3143a984f 100644 --- a/gradle/android.gradle +++ b/gradle/android.gradle @@ -15,7 +15,7 @@ android { buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + consumerProguardFiles 'proguard-rules.pro' } } lintOptions { @@ -23,6 +23,9 @@ android { if (lintFile.exists()) { lintConfig lintFile } + + checkReleaseBuilds !isCi + abortOnError !isCi } } kotlin.sourceSets.all { diff --git a/model/proguard-rules.pro b/model/proguard-rules.pro index f1b424510..c5a8956fb 100644 --- a/model/proguard-rules.pro +++ b/model/proguard-rules.pro @@ -5,17 +5,4 @@ # 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 +-dontwarn io.github.droidkaigi.confsched2019.model.*$Creator