-
-
Save dmarcato/d7c91b94214acd936e42 to your computer and use it in GitHub Desktop.
def toCamelCase(String string) { | |
String result = "" | |
string.findAll("[^\\W]+") { String word -> | |
result += word.capitalize() | |
} | |
return result | |
} | |
afterEvaluate { project -> | |
Configuration runtimeConfiguration = project.configurations.getByName('compile') | |
ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult | |
// Forces resolve of configuration | |
ModuleVersionIdentifier module = resolution.getAllComponents().find { it.moduleVersion.name.equals("play-services") }.moduleVersion | |
String prepareTaskName = "prepare${toCamelCase("${module.group} ${module.name} ${module.version}")}Library" | |
File playServiceRootFolder = project.tasks.find { it.name.equals(prepareTaskName) }.explodedDir | |
Task stripPlayServices = project.tasks.create(name: 'stripPlayServices', group: "Strip") { | |
inputs.files new File(playServiceRootFolder, "classes.jar") | |
outputs.dir playServiceRootFolder | |
description 'Strip useless packages from Google Play Services library to avoid reaching dex limit' | |
doLast { | |
copy { | |
from(file(new File(playServiceRootFolder, "classes.jar"))) | |
into(file(playServiceRootFolder)) | |
rename { fileName -> | |
fileName = "classes_orig.jar" | |
} | |
} | |
tasks.create(name: "stripPlayServices" + module.version, type: Jar) { | |
destinationDir = playServiceRootFolder | |
archiveName = "classes.jar" | |
from(zipTree(new File(playServiceRootFolder, "classes_orig.jar"))) { | |
exclude "com/google/ads/**" | |
exclude "com/google/android/gms/analytics/**" | |
exclude "com/google/android/gms/games/**" | |
exclude "com/google/android/gms/plus/**" | |
exclude "com/google/android/gms/drive/**" | |
exclude "com/google/android/gms/ads/**" | |
} | |
}.execute() | |
delete file(new File(playServiceRootFolder, "classes_orig.jar")) | |
} | |
} | |
project.tasks.findAll { it.name.startsWith('prepare') && it.name.endsWith('Dependencies') }.each { Task task -> | |
task.dependsOn stripPlayServices | |
} | |
} |
In case anyone needs this to make this work when using parallel builds (org.gradle.parallel=true inside gradle.properties), you need to add this snippet at the end :
project.tasks.findAll { it.name.contains(prepareTaskName) }.each { Task task ->
stripPlayServices.mustRunAfter task
}
I've created a fork with this modification here https://gist.github.com/TechFreak/f7ffcbfee5cfe318d31a: but I would prefer if the author added my modifications back to keep everything in one place
When using the above code we are seeing that the added task (stripPlayServices) gets executed every time you build (due to classes.jar being updated) which in turn leads to the dex files being regenerated (adding more time to the build).
However, instead of adding a task to be run after the prepare task for the library you can add it to the prepare task itself (this way it will only run if the library is updated).
afterEvaluate { project ->
Configuration runtimeConfiguration = project.configurations.getByName('compile')
ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult
// Forces resolve of configuration
ModuleVersionIdentifier module = resolution.getAllComponents().find { it.moduleVersion.name.equals("play-services") }.moduleVersion
String prepareTaskName = "prepare${toCamelCase("${module.group} ${module.name} ${module.version}")}Library"
Task prepareTask = project.tasks.findByName(prepareTaskName)
File playServiceRootFolder = prepareTask.explodedDir
// Add the stripping to the existing task that extracts the AAR containing the original classes.jar
prepareTask.doLast {
// First create a copy of the GMS classes.jar
copy {
from(file(new File(playServiceRootFolder, "classes.jar")))
into(file(playServiceRootFolder))
rename { fileName ->
fileName = "classes_orig.jar"
}
}
// Then create a new .jar file containing everything from the first one except the stripped packages
tasks.create(name: "stripPlayServices" + module.version, type: Jar) {
destinationDir = playServiceRootFolder
archiveName = "classes.jar"
from(zipTree(new File(playServiceRootFolder, "classes_orig.jar"))) {
exclude "com/google/ads/**"
exclude "com/google/android/gms/games/**"
exclude "com/google/android/gms/drive/**"
exclude "com/google/android/gms/panorama/**"
}
}.execute()
delete {
delete(file(new File(playServiceRootFolder, "classes_orig.jar")))
}
}
}
I'm including play-services in an android library project instead of the app module but the stripping doesn't seem to work, it works fine with I include play-services in the app module though. Any ideas why it's not working for android library projects?
@erikandre proposal seems to work 👍 But why the last command delete is in another delete? typo?
Is it me or does this not work on android 2.3.x ? I keep getting a lot of:
W/dalvikvm﹕ Link of class 'LsomeLibraryClass' failed
but the same apk works fine on my 4.2.2 device
it doesn't work on linux. I'm still getting error about dex limit
I can't get it to work either. My project is an android library project itself and I get this :
- What went wrong:
A problem occurred configuring root project 'Android_NEW_SDK_develop'.Failed to notify project evaluation listener.
groovy.lang.MissingMethodException: No signature of method: org.gradle.api.internal.artifacts.result.DefaultResolutionResult.getAllComponents() is applicable for argument types: () values: []
Is there any way to move this peace of code in separate file to keep build.gradle clean?
This the result of all the changes discussed here, condensed in a gist: https://gist.github.com/Takhion/10a37046b9e6d259bb31
Working for me on:
- Play Services 5.0.89
- Android Gradle plugin 0.13
- Gradle 2.1
@b1uebyte use this:
apply from: 'strip_play_services.gradle'
This script is NO longer necessary now you can use the new MultiDex functionality that is built right in the support library. More info: http://blog.osom.info/2014/10/multi-dex-to-rescue-from-infamous-65536.html
I have an error:
Cannot get property 'moduleVersion' on null object
@techfreak that's not true, by not using the script you would still include all the unneeded classes (unless you use Proguard), making your apk much bigger than needed! Plus, using MultiDex has an overhead and should be done only when really needed.
I made this slight change to only do this stripping in debug
builds. Since I ProGuard the code in other builds and I have no 64k limit issue
project.tasks.findAll { it.name.startsWith('prepare') && it.name.endsWith('Dependencies') && it.name.contains('Debug') }.each { Task task ->
I tried this and it works well for me - I the same as @robUx4 and made it work only with Debug builds.
Does it actually continue to strip the play-services jar each time you compile, or is it smart enough to know it's already been done and skips that step? Just wondering if it does it each time how much it adds to compile time.
How to run this task from terminal for release build? It does work when first run from the Android Studio but after clean gradlew installRelease doesn't work any more (method ID not in [0, 0xffff]: 65536). I have to build the app at least once from Android Studio after each clean. Is there any way to strip play services from terminal without using IDE?
Hi,
I updated this script to prevent restripping the play-services jar on each build if the configuration has not changed. See the fork at https://gist.github.com/CedricGatay/4e21ce855bd2562f9257.
Things to keep / exclude are declared at the top, and a simple file named after Play Services GAV identifier is put in build/intermediates/tmp
as well as the stripped version of the jar to be swapped on next build if the content of the .marker
file has not changed.
Seems to work so far in our current project, allows us to stay within the 64k limit by removing what's unnecessary.
Don't forget to use it in your build.gradle
file this way :
apply plugin: 'com.android.application'
apply from: 'strip_play_services.gradle'
With the latest 6.x of gms you might need to add the following to keep the class count to a minimum:
exclude "com/google/android/gms/fitness/**"
exclude "com/google/android/gms/wearable/**"
exclude "com/google/android/gms/wallet/**"
exclude "com/google/android/gms/cast/**"
exclude "com/google/android/gms/ads/doubleclick/**"
exclude "com/google/android/gms/ads/mediation/**"
exclude "com/google/android/gms/ads/purchase/**"
exclude "com/google/android/gms/ads/search/**"
exclude "com/google/android/gms/analytics/ecommerce/**"
exclude "com/google/android/gms/common/images/**"
Can you explain for dummies where to put this script (in module build.gradle or in application build.gradle) and how to run it.
Because I put it in module build.gradle and get the following error
Error:(157) Execution failed for task ':phase-android-beta:stripPlayServices'.
Cannot expand ZIP 'D:\Projects\phase\phase-android-beta\build\intermediates\exploded-aar\com.google.android.gms\play-services\6.1.71\classes_orig.jar' as it does not exist.
I fix it me helped @techfreak comment:
In case anyone needs this to make this work when using parallel builds (org.gradle.parallel=true inside gradle.properties), you need to add this snippet at the end :
project.tasks.findAll { it.name.contains(prepareTaskName) }.each { Task task ->
stripPlayServices.mustRunAfter task
}
I've created a fork with this modification here https://gist.github.com/TechFreak/f7ffcbfee5cfe318d31a: but I would prefer if the author added my modifications back to keep everything in one place
Google released GooglePlayServicesLib 6.5 you can now only compile the parts of GooglePlayServices you need http://android-developers.blogspot.de/2014/11/google-play-services-65.html
Starting with version 6.5, of Google Play services, you’ll be able to pick from a number of individual APIs, and you can see which ones have their own include files in the documentation. For example, if all you want to use is Maps, you would instead have:
compile 'com.google.android.gms:play-services-maps:6.5.87'
Note that this will transitively include the ‘base’ libraries, which are used across all APIs. You can include them independently with the following line:
compile 'com.google.android.gms:play-services-base:6.5.87'
http://android-developers.blogspot.de/2014/12/google-play-services-and-dex-method.html
Google Play services API | Description in build.gradle |
---|---|
Google+ | com.google.android.gms:play-services-plus:6.5.+ |
Google Account Login | com.google.android.gms:play-services-identity:6.5.+ |
Google Activity Recognition | com.google.android.gms:play-services-location:6.5.+ |
Google App Indexing | com.google.android.gms:play-services-appindexing:6.5.+ |
Google Cast | com.google.android.gms:play-services-cast:6.5.+ |
Google Drive | com.google.android.gms:play-services-drive:6.5.+ |
Google Fit | com.google.android.gms:play-services-fitness:6.5.+ |
Google Maps | com.google.android.gms:play-services-maps:6.5.+ |
Google Mobile Ads | com.google.android.gms:play-services-ads:6.5.+ |
Google Panorama Viewer | com.google.android.gms:play-services-panorama:6.5.+ |
Google Play Game services | com.google.android.gms:play-services-games:6.5.+ |
Google Wallet | com.google.android.gms:play-services-wallet:6.5.+ |
Android Wear | com.google.android.gms:play-services-wearable:6.5.+ |
Google Actions Google Analytics Google Cloud Messaging | com.google.android.gms:play-services-base:6 |
If you don't want to use play services 6.5+ try this:
compile('com.google.android.gms:play-services:6.1.71') {
exclude group: "com/google/ads/**"
exclude group: "com/google/android/gms/analytics/**"
exclude group: "com/google/android/gms/games/**"
exclude group: "com/google/android/gms/maps/**"
exclude group: "com/google/android/gms/panorama/**"
exclude group: "com/google/android/gms/plus/**"
exclude group: "com/google/android/gms/drive/**"
exclude group: "com/google/android/gms/ads/**"
exclude group: "com/google/android/gms/wallet/**"
exclude group: "com/google/android/gms/wearable/**"
}
I am trying to get rid of the unused library for Google Play services 7.3 with the previous code but it does not work.
This is the code:
compile ('com.google.android.gms:play-services:7.3.+') {
exclude group: "com/google/ads/**"
exclude group: "com/google/android/gms/ads/**"
exclude group: "com/google/android/gms/analytics/**"
exclude group: "com/google/android/gms/appindexing/**"
exclude group: "com/google/android/gms/appinvite/**"
exclude group: "com/google/android/gms/appstate/**"
exclude group: "com/google/android/gms/cast/**"
exclude group: "com/google/android/gms/drive/**"
exclude group: "com/google/android/gms/fitness/**"
exclude group: "com/google/android/gms/games/**"
exclude group: "com/google/android/gms/identity/**"
exclude group: "com/google/android/gms/location/**"
exclude group: "com/google/android/gms/maps/**"
exclude group: "com/google/android/gms/nearby/**"
exclude group: "com/google/android/gms/panorama/**"
exclude group: "com/google/android/gms/plus/**"
exclude group: "com/google/android/gms/safetynet/**"
exclude group: "com/google/android/gms/wallet/**"
exclude group: "com/google/android/gms/wearable/**"
}
And I still can see them in the console:
:app:prepareComGoogleAndroidGmsPlayServices730Library
:app:prepareComGoogleAndroidGmsPlayServicesAds730Library
:app:prepareComGoogleAndroidGmsPlayServicesAnalytics730Library
:app:prepareComGoogleAndroidGmsPlayServicesAppindexing730Library
:app:prepareComGoogleAndroidGmsPlayServicesAppinvite730Library
:app:prepareComGoogleAndroidGmsPlayServicesAppstate730Library
:app:prepareComGoogleAndroidGmsPlayServicesBase730Library
:app:prepareComGoogleAndroidGmsPlayServicesCast730Library
:app:prepareComGoogleAndroidGmsPlayServicesDrive730Library
:app:prepareComGoogleAndroidGmsPlayServicesFitness730Library
:app:prepareComGoogleAndroidGmsPlayServicesGames730Library
:app:prepareComGoogleAndroidGmsPlayServicesGcm730Library
:app:prepareComGoogleAndroidGmsPlayServicesIdentity730Library
:app:prepareComGoogleAndroidGmsPlayServicesLocation730Library
:app:prepareComGoogleAndroidGmsPlayServicesMaps730Library
:app:prepareComGoogleAndroidGmsPlayServicesNearby730Library
:app:prepareComGoogleAndroidGmsPlayServicesPanorama730Library
:app:prepareComGoogleAndroidGmsPlayServicesPlus730Library
:app:prepareComGoogleAndroidGmsPlayServicesSafetynet730Library
:app:prepareComGoogleAndroidGmsPlayServicesWallet730Library
:app:prepareComGoogleAndroidGmsPlayServicesWearable730Library
What am I doing wrong?
As of Play service 8.3.0 you can selectively choose which individual library used in the app. So there's no need to exclude anymore.
https://developers.google.com/android/guides/setup#split
I paste it to the bottom of app's build.gradle file and it failed : "Cannot get property 'moduleVersion' on null object"
What should I do ?
Cannot get property 'moduleVersion' on null object. I got this problem and the sync failed....
Cannot get property 'moduleVersion' on null object. I got this problem and the sync failed....
at
ModuleVersionIdentifier module = resolution.getAllComponents().find { it.moduleVersion.name.equals("play-services") }.moduleVersion
Happened to me to, so I printed it:
`
ModuleVersionIdentifier module = resolution.getAllComponents().find {
System.out.println("MODULE NAME ====> " + it.moduleVersion.name);
/*it.moduleVersion.name.equals("play-services")*/
}.moduleVersion
`
....
MODULE NAME ====> recyclerview-v7
MODULE NAME ====> core
MODULE NAME ====> gson
MODULE NAME ====> hirondelle-date4j
MODULE NAME ====> play-services-analytics
:app:preBuild
:jawampaLib:preBuild
....
As You can see, there is no "play-services", but "play-services-analytics" (in my case) :)
Same happens in my case.
@hzsweers did you ever find a solution?
I noticed right away that this happens on every build:
Input file src/build/intermediates/exploded-aar/com.google.android.gms/play-services/5.0.89/classes.jar has changed.
command: dx --dex --output src/build/intermediates/pre-dexed/debug/classes-bcce6309a4833bee1a1a4249e969fc418be68a9a.jar src/build/intermediates/exploded-aar/com.google.android.gms/play-services/5.0.89/classes.jar