Custom build Android app from command line - android

I'm trying to make a batch file with some commands, so once it runs, it will change some strings in the files, build the project and generate the APK signed.
The strings I need to change are:
- The package name (com.company.project)
- Some images (like icons, splash screen, ...)
- some irrelevant string that are specific from the app.
For the last 2 things I know how to do it, but for the package name I feel there is something wrong about just find and replace all the occurrences of that string in the root folder of the app (including subdirectories).
Is there any way or command that ant has for doing this?
Also I ran into an issue while running the command ant release.
I went to my root folder, ran the command and it gets errors.
So I had to go to eclipse, clean the project and let it autobuild (with no generation of APK since it does that when you try to run it on a device) so at that point my bin folder just contains the folders: classes, dexedLibs, res and the Manifest.xml file.
Then I can go to the CL and run ant release.
So is there any way to do all this from CL? Something like clean and build so I can run ant release command after with no issues?
NOTE: for find and replace I use an .exe called FNR that does the job
EDIT:
I'm now using gradle and can build changing the package name but there is still a few things I want to do in the build.gradle file and can't make it work.
This is build.gradle:
task("hello"){
println "Hello world!!"
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.8.+'
}
}
apply plugin: 'android'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
}
android {
compileSdkVersion 19
buildToolsVersion "19.0.3"
productFlavors {
flavor1 {
packageName "com.testCompany.testProject"
}
}
signingConfigs {
release {
storeFile file("keystore/android.keystore")
storePassword 'blah blah'
keyAlias "blah blah"
keyPassword 'blah blah blah'
}
}
buildTypes {
flavor1 {
zipAlign true
sourceSets {
main {
signingConfig signingConfigs.release
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
}
}
}
I am pretty sure I'm doing things wrong.
So I want to:
- Change some strings in the res/strings.xml file.
- Change the icon/png files in the res/drawable.... folder for custom ones.
I have completely no idea of how to do it. I tried:
buildTypes{
flavor1{
copy{
from('src/res/'){
include '**/*.xml'
filter{String line -> line.replaceAll(string_to_be_replaced, replaced_string)}
}
into '$buildDir/res'
}
}
}
but nothing

The strings I need to change are: - The package name (com.company.project)
If you are changing these things based upon whether this is a debug build or a release build, you can specify a suffix on the package name for a build type:
android {
buildTypes {
debug {
packageNameSuffix ".debug"
}
}
}
Or, if you are changing these things for anything else, you can create product flavors and replace the package name per flavor:
android {
productFlavors {
flavor1 {
packageName "com.example.flavor1"
}
flavor2 {
packageName "com.example.flavor2"
}
}
}
Some images (like icons, splash screen, ...) - some irrelevant string that are specific from the app.
You can create source sets for build types (e.g., src/debug/) or product flavors (e.g., src/flavor1/) and have replacement versions of resources in them. That will handle your images, as well as your "irrelevant string" if you define it as a string resource.
Source sets can also have Java code, though that gets incrementally more complex, so I would recommend that you use the string resources and replacement resources instead.

Related

Product flavors in Android Studio [duplicate]

This question already has answers here:
Reference different assets based on build flavor
(2 answers)
Closed 3 years ago.
I can not get product flavours working. I have this gradle
apply plugin: 'com.android.application'
android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 24
compileSdkVersion 27
}
signingConfigs {
release {
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
signingConfig signingConfigs.release
}
}
repositories {
maven { url "https://jitpack.io" }
}
flavorDimensions "dim1", "dim2", "dim3"
productFlavors {
flavor1 {
dimension "dim1"
applicationId "com.example.dim1.app"
}
flavor3 {
dimension "dim2"
applicationId "com.example.dim2.app"
}
flavor3 {
dimension "dim3"
applicationId "com.example.dim3.app"
}
}
sourceSets {
flavor1 {
java.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/java/"]
manifest.srcFile "W:/android-studio-projects/sharedid/app/src/example1/AndroidManifest.xml"
assets.srcDirs = ["W:/android-studio-projects/sharedid/app/src/example1/assets/"]
resources.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/res/", "W:/android-studio-projects/sharedid/app/src/example1/res/"]
}
flavor2 {
java.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/java/"]
manifest.srcFile "W:/android-studio-projects/sharedid/app/src/example2/AndroidManifest.xml"
assets.srcDirs = ["W:/android-studio-projects/sharedid/app/src/example2/assets/"]
resources.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/res/", "W:/android-studio-projects/sharedid/app/src/example2/res/"]
}
flavor3 {
java.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/java/"]
manifest.srcFile "W:/android-studio-projects/sharedid/app/src/example3/AndroidManifest.xml"
assets.srcDirs = ["W:/android-studio-projects/sharedid/app/src/example3/assets/"]
resources.srcDirs = ["W:/android-studio-projects/sharedid/app/src/main/res/", "W:/android-studio-projects/sharedid/app/src/example3/res/"]
}
}
}
dependencies {
api 'com.google.android.gms:play-services-maps:15.0.0'
api 'com.google.android.gms:play-services-location:15.0.0'
api 'com.android.support:appcompat-v7:27.1.1'
api 'com.github.PhilJay:MPAndroidChart:v2.0.8'
}
...
When I got to "Build | Select variant" I can only select
Module:app
Build Variant:flavor1Flavor2Flavor3Debug,flavor1Flavor2Flavor3Release
I would have liked to get
the following build variants: flavor1Debug,flavor2Debug,flavor3Debug,flavor1Release,flavor2Release,flavor3Release
I have tried "File | Sync project with gradle file"
...
I get this error
Caused by: java.lang.RuntimeException: Cannot read packageName from
W:\android-studio-projects\sharedid\app\src\main\AndroidManifest.xml
I have tried to both
have no such file (hoping it would take the product flavor one?)
have the "main" manifest only define shared stuff between all product flavors
Just try like below,
flavorDimensions "dim1"
productFlavors {
flavor1 {
dimension "dim1"
applicationId "com.example.dim1.app"
}
flavor3 {
dimension "dim1"
applicationId "com.example.dim2.app"
}
flavor3 {
dimension "dim1"
applicationId "com.example.dim3.app"
}
}
For more details about build variant see this link
I think there are two unrelated problems :
Currently you have 2 build types (the automatically created debug and release) and 3 dimensions (dim1, dim2 and dim3), each one having 1 variant (flavor1 for dim1, flavor2 for dim2, ...)
this gives at most :
2 * 1 * 1 * 1 = 2 combinations
You should switch to 2 build types and 1 dimension (say dim1) having 3 variants (flavor1, flaver2 and flavor3) to have :
2 * 3 = 6 apks
You should have a main manifest. Unlike other resources the manifest is not simply overriden but merged from multiple sources (see Merge Multiple Manifest Files for more details).
It should at least contains a package name (possibly different from the final applicationId(s)) as explained by this note from Configure Product Flavors :
Note : You still need to specify a package name using the package attribute
in the main/ manifest file. You must also use that package name in
your source code to refer to the R class, or resolve any relative
activity or service registration. This allows you to use applicationId
to give each product flavor a unique ID for packaging and
distribution, without having to change your source code.
I would have liked to get
the following build variants:
flavor1Debug,flavor2Debug,flavor3Debug,flavor1Release,flavor2Release,flavor3Release
For this, you need to define the same dimension for all flavors.
I get this error
Caused by: java.lang.RuntimeException: Cannot read packageName from
W:\android-studio-projects\sharedid\app\src\main\AndroidManifest.xml
You get this error because the path is not reachable.
Just think, how can app find W: when it is running?
So, you need to use a relative path here.
Also from official documentation (https://developer.android.com/studio/build/build-variants#configure-sourcesets):
If you have sources that are not organized into the default source set
file structure that Gradle expects, as described above in the section
about creating source sets, you can use the sourceSets block to change
where Gradle looks to gather files for each component of a source set.
You don't need to relocate the files; you only need to provide Gradle
with the path(s), relative to the module-level build.gradle file,
where Gradle should expect to find files for each source set component

Gradle multi-module dependecy issue

Information
So I have this project structure that looks like this:
Project root:
| Resources-module (uses the packagename: com.derk.application.resources)
-+ src
---+ main
-----+ res
| Core-module (uses the packagename: com.derk.application)
-+ src
---+ main
-----+ src
| Brand-module (uses whatever packagename of the customer domain)
Resources-module contains the res files.
Core contains a git-module that contains code for a code-base we keep updated.
Brand contains build.gradle where I setup the packagename of our customer and bind the main/res directory to the resource-module main/res, and then the main/src to the core-module main/src folder.
Like this:
android.sourceSets.main {
manifest.srcFile "src/main/AndroidManifest.xml"
res.srcDirs = ["$rootDir/resources-module/src/main/res"]
java.srcDirs = ["$rootDir/core-module/src/main/java", "src/main/java"]
}
}
I do not wish to alter the Core-module sources under any circumstances without it being pushed up to the master repository, meaing I only make global changes/fixes for all projects that uses core-module. This is why i tried this structure out.
The sourcefiles in the core-module will loads the imports
import com.derk.application.resources.R;
import com.derk.application.resources.BuildConfig;
to handle the resource generated content from gradle/idea
Since Brand-module is due to having packagename changes, I have to use some sort of middlemodule that holds the R and BuildConfig for easy deployment, so that the core-sources indeed never have local modifications.
When i try to refresh gradle for the brand-module, i do not get any issues, and android studio seems to find the R.java and BuildConfig.java just fine in the com.derk.application.resources when I check out the linkage in Android Studio
HOWEVER
When I try to run Brand-module, i get:
"Execution failed for task ':core-module:compileReleaseJava'."
and it now instead shows me:
Error:(20, 39) error: package com.derk.application.resources does not exist
even thought I have added
dependencies {
compile project(':resources-module')
}
to the build.gradle of core-module.
So the question is:
How do I setup gradle to handle this kind of cross-module dependency?
Keep in mind, I do not wish to alter the packagename for the core-module imports for each new project I setup, because we get local changes made to a gitmodule that is used for several projects.
/.ps
Currently i can without problem run module resources-module and have the app running, with the static packagename i've chosen for it. But that is also the problem, I want to keep it static, and hence that is why i introduced the third module.
You do that in the wrong way. What you are trying to achive is implementing a normal library module (which you use for every customer). Then you can create a new Module from the type application which uses the shared module. In that case you don't need to mess around with the path of the shared module.
For the case that your shared module is in another directory than the project root you can use this settings.gradle:
include ':CustomerX', ':SharedModule'
project(':SharedModule').projectDir = new File('../../some/where/else')
If not you can omit the last line.
When you keep a old directory structure you should try using this build.gradle:
apply plugin: 'com.android.library'
android {
compileSdkVersion 9
buildToolsVersion '21.0.1'
defaultConfig {
minSdkVersion 9
targetSdkVersion 21
}
sourceSets {
main {
assets.srcDirs = ['assets']
res.srcDirs = ['res']
aidl.srcDirs = ['src']
resources.srcDirs = ['src']
renderscript.srcDirs = ['src']
java.srcDirs = ['src']
manifest.srcFile 'AndroidManifest.xml'
}
}
}
dependencies {
// just as example
compile 'com.android.support:support-v4:21.0.2'
}
You customers build.gradle should look e.g. like this:
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion '21.0.1'
defaultConfig {
minSdkVersion 9
targetSdkVersion 21
signingConfigs {
release {
storeFile file("your.keystore")
storePassword 'pwd1'
keyAlias "alias"
keyPassword 'pwd2'
}
}
buildTypes {
debug {
debuggable true
minifyEnabled false
shrinkResources false
}
release {
debuggable false
jniDebuggable false
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
dependencies {
// dependencies of the main project
compile 'com.android.support:support-v4:21.0.0'
compile 'com.android.support:appcompat-v7:21.0.0'
compile project(':SharedModule')
}

How to setup flavors for android gradle project? Mysterious duplicate class error

I have created simple test project: the goal is to show the message 'hello' by pressing a button on the screen. The first flavor build should write the message to the system log. The second flavor build should show a toast with message. How can this be achieved using gradle please?
My build.gradle:
apply plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion "19.0.1"
defaultConfig {
minSdkVersion 14
targetSdkVersion 19
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
productFlavors {
toast {
}
log {
}
}
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/log/java']
}
toast {
java.srcDirs = ['src/main/java', 'src/toast/java']
}
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
File structure:
Flavor log contains single class Messenger with method showMessage(Context context, CharSequence text) and prints text using Log.i(tag, msg)
Flavor toast contains single class Messenger with method showMessage(Context context, CharSequence text) and shows toast with some text.
Main sources don't contain this class.
Why does the error duplicate class:com.test.flavortest.Messenger appear? Each flavor has a set of different non-crossing source paths?
Full sample project, zipped
In your sourcesets definition, you seem to be adding the log sources to main:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/log/java']
}
toast {
java.srcDirs = ['src/main/java', 'src/toast/java']
}
}
main is the default sourceset included in all flavors. This would cause a duplicate class (Messenger) being loaded when building the toast flavor.
Try specifying the log and toast sourcesets only:
sourceSets {
log {
java.srcDirs = ['src/main/java', 'src/log/java']
}
toast {
java.srcDirs = ['src/main/java', 'src/toast/java']
}
}
Your file structure seems to match the default, so an even better solution would be to remove the sourcesets block entirely. src/main/java is included by default, and then src/flavor/java is added afterwards automatically.
Use assembleToast / assembleLog to Build an specific Flavour.
Same for installToast e.g
The global assemble will use every File in the directory.
All source code in the java/ directories are compiled together to generate a single output.
Note: For a given build variant, Gradle throws a build error if it encounters two or more source set directories that have defined the same Java class. For example, when building a debug APK, you cannot define both src/debug/Utility.java and src/main/Utility.java. This is because Gradle looks at both these directories during the build process and throws a 'duplicate class' error. If you want different versions of Utility.java for different build types, you can have each build type define its own version of the file and not include it in the main/ source set.

Instruct Android Gradle script to delete unaligned apks and clean artifact files

I started using Gradle build system a few days ago and got the script to work as I wanted, here it is:
buildscript {
repositories {
mavenCentral()
}
}
dependencies {
classpath 'com.android.tools.build:gradle:0.6.+'
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 17
buildToolsVersion '18.0.1'
productFlavors {
flavor1 {
packageName "flavor1"
}
flavor2 {
packageName "flavor2"
}
flavor3 {
packageName "flavor3"
}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
signingConfigs {
release {
storeFile file("test.keystore")
storePassword "*****"
keyAlias "****"
keyPassword "*****"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
}
}
As you can see there is nothing fancy here just building 3 flavours of the app and signing them with the same key. I just run gradle assembleRelease and after 20 seconds I have my apks in build folder. But the problem is that there are other files generated that I don't want for example appname-release-unaligned.apk.
I know that this file is needed before apk can be signed but would like to add a task to delete these files in my gradle script?
Also if it's possible I would like to remove all other (shell I say artefact files) generated during build. Essentially I would like to run something like gradle clean but leave the build apk files. How do I do this?
BONUS:If anyone has pointers on how can I optimise this script and enable zip align and proguard (without custom rules just default obfuscation is ok) that would also help me since I am very new to gradle and none of the tutorials I followed explains these steps.
UPDATE February 2018.
This block will cause a build error using Android Gradle plugin 3.0 or above. See 'deepSymmetry's comment below.
The "fix" is to delete the block altogether and the plugin's default behavior will automatically clean up the intermediate temporary apks (ex: app-debug-unaligned.apk).
Pretty old topic but here is modern solution for deleting unnecessary 'unaligned' file. This is quite handy especially on CI servers to save some space.
That's a shame that plugin does not provide hook for 'zipAlign' task so we'll need to hook on 'assemble' task which goes after 'zipAlign'.
Works with last gradle plugin 1.2.0 (gradle-2.4) but should be valid for 1.+
// delete unaligned files
android.applicationVariants.all { variant ->
variant.assemble.doLast {
variant.outputs.each { output ->
println "aligned " + output.outputFile
println "unaligned " + output.packageApplication.outputFile
File unaligned = output.packageApplication.outputFile;
File aligned = output.outputFile
if (!unaligned.getName().equalsIgnoreCase(aligned.getName())) {
println "deleting " + unaligned.getName()
unaligned.delete()
}
}
}
}
And another one if your prefer to check zipAlignEnable flag but in this case you'll be tied to "unaligned" constant in filename because release builds with zipAlignEnabled=true AND without signingConfig skip 'zipAlign' task and produce only one file: 'app-release-unsigned.apk'.
// delete unaligned files
android.applicationVariants.all { variant ->
variant.assemble.doLast {
variant.outputs.each { output ->
println "aligned " + output.outputFile
println "unaligned " + output.packageApplication.outputFile
File file = output.packageApplication.outputFile;
if (variant.buildType.zipAlignEnabled && file.getName().contains("unaligned")) {
println "deleting " + file.getName()
file.delete()
}
}
}
}
I am using the first one in case anyone cares.
I noticed there is some activity on this question from time to time so here is the way I solved the problem if it helps someone. Just define new task to copy file and then set execution order.
task copyTask(type: Copy) {
from 'build/apk'
into 'apks'
exclude '**/*-unaligned.apk'
}
task allTask(dependsOn: ['clean', 'assembleRelease', 'copyTask']){
clean.mustRunAfter copyTask
copyTask.mustRunAfter assembleRelease
}
then just call this allTask when you want to do a build.
I can at least answer your bonus-question:
buildTypes {
release {
runProguard true
signingConfig signingConfigs.release
}
}
If you have specific proguard-rules, just enter this line to your defaultConfig or to your product flavors:
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
The first proguard rule is the generla one from your Android SDK, the second one is from your module-directory.
ZipAlign is enabled by default if you build you project with the build variant release.
Just a little bit modified answer from #PSIXO, сonsidering Android Studio 1.5, running test cases and renaming apk file for better CI integration:
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.mydemoci"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName "1.0"
archivesBaseName = "$applicationId-v$versionName"
}
}
task copyOutputApk(type: Copy) {
from 'build/outputs/apk'
into 'apk'
exclude '**/*-unaligned.apk'
}
task buildTestDebug(dependsOn: ['clean', 'assembleDebug', 'check', 'copyOutputApk']) {
check.mustRunAfter assembleDebug
copyOutputApk.mustRunAfter check
clean.mustRunAfter copyOutputApk
}
To start build run gradlew buildTestDebug

built flavors of a project

I want to build different flavors of a project (only the res folders have different contents), but it doesn't work.
So here is my build.gradle file (like in this question Custom old Android project structure in Gradle):
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.4'
}
}
apply plugin: 'android'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile project('<path>:actionbarsherlock')
}
android {
compileSdkVersion 17
buildToolsVersion "17.0.0"
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
buildTypes {
debug {
packageNameSuffix ".debug"
}
release{
packageNameSuffix ".release"
}
android.sourceSets.flavor1 {
res.srcDir = ['res_flavor1']
}
android.sourceSets.flavor2 {
res.srcDir = ['res_flavor2']
}
productFlavors {
flavor1 {
packageName "androidstudio.test.flavor1"
}
flavor2 {
packageName "androidstudio.test.flavor2"
}
}
}
}
Structure of my folders (I want to change just one layout for each flavor):
my_project
src
res
res_flavor1
res_flavor2
I don't see any changes in the build variants, so when I export an apk its of the main directory (androidstudio.test).
Whats wrong with my file?
My Android Studio Version: 0.1.3.
If you need more information, say what and I will post it.
Thanks!
[EDIT]
#Greg:
I changed the res-folder in my question above, but I still get just two APKs in my project "out" folder (path: "out/production/androidstudio.test"): androidstudio.test.apk and androidstudio.test.unaligned.apk (the project and module name is: android.studio - I edited this names in my question above, otherwise its a bit confusing :)).
The different buildTypes should be generated automatically, shouldn't they? And to generate them I go to: Run -> Run "androidstudio.test".
I also looked in the log files of android-studio, but there is no error.
Here is a Screenshot of my Package Explorer, perhaps I made here a mistake?
I really don't understand why the flavors aren't build.
You want to be setting res.srcDirs = ['res_flavor1'] instead of resources.srcDir = ['res_flavor1']. The naming is a bit confusing, but the "resources" directory isn't the same as the Android "res" directory.

Categories

Resources