I'm using Android Studio and desperately trying to get gradle to publish both my debug & release aar's to artifactory with separate repoKeys. Preferably as two separate tasks. I have read this excellent guide, but I don't want to change my gradle file each time in order to deploy.
I can easily create two pom files, but have not managed to make the artifactory plugin dynamic enough to switch my two rep keys.
I feel this is the closest I've come but it's not quite there:
String art_repo = ''
artifactory {
contextUrl = 'https://www.myjfrogrepo.com'
publish {
repository {
// The Artifactory repository key to publish to
repoKey = "${art_repo}"
I have then created a custom task which sets the art_repo variable.
task debugPublishTojFrog {
group "Publishing"
art_repo = 'libs-snapshot-local'
art_publicationName = 'debugAar'
doLast {
println "Successfully published ${art_repo} to jFrog"
}
}
debugPublishTojFrog.finalizedBy artifactoryPublish
This responds with the error
Target repository cannot be empty
This leads me to think the repoKey is set at build time and not at run time because my custom task's changes seem to be ignored.
Any suggestions really would be very much appreciated.
A standard way to do this in gradle is to test for the version you are about to publish.
This is exactly what the guide you link to advocates:
repoKey = libraryVersion.endsWith('SNAPSHOT') ? 'libs-snapshot-local' : 'libs-release-local'
What is wrong with this solution?
Edit: If the problem lies with having to change gradle or the gradle.properties file, why not use properties from the command line.
repoKey = project.hasProperty('releaseVersion') ? 'libs-release-local' : 'libs-snapshot-local'
And build your snapshot with
gradle build
And build your release with
gradle build -PreleaseVersion=1.0.0-GA
You can also use this releaseVersion property at several other locations, like your software metadata, your publication information...
Related
I have to add the Analytics tool Sentry to our Android project. In order to make it work, one needs to create mappings for the obfuscated code (from Proguard/R8) and upload it later to Sentry.
On the website https://docs.sentry.io/platforms/android/ it is even described how to do that.
There it is written that one needs to create a gradle task looking like this:
gradle.projectsEvaluated {
android.applicationVariants.each { variant ->
def variantName = variant.name.capitalize();
def proguardTask = project.tasks.findByName(
"transformClassesAndResourcesWithProguardFor${variantName}")
def dexTask = project.tasks.findByName(
"transformClassesWithDexFor${variantName}")
def task = project.tasks.create(
name: "processSentryProguardFor${variantName}",
type: Exec) {
workingDir project.rootDir
commandLine *[
"sentry-cli",
"upload-proguard",
"--write-properties",
"${project.rootDir.toPath()}/app/build/intermediates/assets" +
"/${variant.dirName}/sentry-debug-meta.properties",
variant.getMappingFile(),
"--no-upload"
]
}
dexTask.dependsOn task
task.dependsOn proguardTask
}
}
This shall wait until Proguard is finished, than copy this properties file to the assets. However, when I add this to my Android gradle script I get the error:
Could not create task
':app:processSentryProguardForPlayStoreStagingDebug'.
No signature of method: java.util.ArrayList.multiply() is applicable for argument types: (ArrayList) values: [[sentry-cli, upload-proguard,
--write-properties, {Application-Path}/app/build/intermediates/assets/playStoreStaging/debug/sentry-debug-meta.properties,
...]] Possible solutions: multiply(java.lang.Number),
multiply(java.lang.Number)
I assume there is something wrong with the multiplication symbol * before the commandLine array. But when I remove it I get the error
Could not create task
':app:processSentryProguardForPlayStoreStagingDebug'.
Cannot cast object 'sentry-cli' with class 'java.lang.String' to class 'int'
So I tried to test this with only that line
commandLine "sentry-cli", ...
Which gave me another error
What went wrong: Cannot invoke method dependsOn() on null object
Thus I assume something went really wrong with that gradle script since it seems the dependend task can't be found. Does anyone have any idea how to fix this (or optionally have any other idea how to copy that sentry-debug-meta.properties file to my assets in another way, once Proguard/R8 is finished)?
Thanks!
-------- EDIT --------
I noticed something important.
The gradle tasks are defined in a different name than what was defined in the manual. Looking at my tasks I have them named
transformClassesAndResourcesWithR8For...
and
transformClassesWithDexBuilderFor...
However, I print the variantName then for checking but it seems my tasks are incomplete.
In my tasks list there exist
transformClassesAndResourcesWithR8ForPlayStoreStagingDebug
but not
transformClassesAndResourcesWithR8ForPlayStoreStagingRelease
and thus the task can't be found. I think that is the real problem here. So where are these gradle tasks defined?
------- EDIT 2 --------
Okay I noticed something strange here. Some variants don't have tasks. It makes sense that DEBUG tasks don't have R8 tasks but I found this here:
Variant: PlayStoreStagingRelease DexTask is null
Variant: PlayStorePreviewRelease DexTask is null
Variant: HockeyAppRelease DexTask is null
Variant: LocalServerRelease DexTask is null
Variant: PlayStoreProductionRelease DexTask is null
So how can this be?
I'd recommend using the Sentry Gradle integration (Gradle plugin) which is described here https://docs.sentry.io/platforms/android/#gradle-integration
The official Android Gradle plugin changed its task names over versions, Gradle version also affects those code snippets.
Google also replaced Proguard with R8 and it also affected those code snippets.
Is there a reason why not using the Sentry Gradle integration? if so, We'll be looking into updating them.
Thanks.
java.util.ArrayList.multiply() hints for that * in front of the [ ] list, which looks strange to me. Try removing the *[ ], only keeping List<String> (there's no ArrayList expected, to begin with):
commandLine "sentry-cli", "upload-proguard", "--write-properties", "${project.rootDir.toPath()}/app/build/intermediates/assets/${variant.dirName}/sentry-debug-meta.properties", variant.getMappingFile(), "--no-upload"
You'd have to look up how your tasks are actually being called, but it should be something alike:
def r8Task = project.tasks.findByName("transformClassesAndResourcesWithR8For${variantName}")
def d8Task = project.tasks.findByName("transformClassesWithDexBuilderFor${variantName}")
With a null check, because not every variant might have minifyEnabled true set:
if(r8Task != null) {
d8Task.dependsOn task
task.dependsOn r8Task
}
Maybe even a previous null check is required, because variant.getMappingFile() needs R8.
And that some flavors have no D8 task might be based upon the absence of code (nothing to do).
Here's a summary of the steps that I followed for integrating Sentry with my Android app. These steps are to ensure the sentry gradle plugin works as expected and automatically uploads the proguard mapping files, without you having to worry about uploading using cli. I assume you would have setup the Sentry SDK as described here:
https://docs.sentry.io/platforms/android/#integrating-the-sdk
Ensure you have Android Studio gradle plugin 3.5.0 (Not 3.6.x, that seems to break the sentry plugin. I observed that the sentry proguard or native symbol upload tasks are not configured or executed at all). This value should be in your root project's build.gradle in dependencies block
Provide a sentry.properties file the root folder of your project. The sentry.properties file should have the following values at minimum:
defaults.project=your_sentry_project_name
defaults.org=your_sentry_org_name
auth.token=sentry_project_auth_token
You can get info about generating auth tokens here: https://sentry.io/settings/account/api/auth-tokens/
(Optional: If you have build flavors) In my case, I have different flavors for my app. So, I had to put the sentry.properties inside my flavor specific folder in /app/src/ folder. Then, I wrote a gradle task to copy the flavor specific sentry.properties file into the project's root folder during gradle's configuration phase. Example:
task copySentryPropertiesTask {
if (getBuildFlavor() != null && !getBuildFlavor().isEmpty()) {
println("Copying Sentry properties file: ${getBuildFlavor()}")
copy {
from "src/${getBuildFlavor()}/"
include "sentry.properties"
into "../"
}
}
}
def getBuildFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern;
if (tskReqStr.contains("assemble"))
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher(tskReqStr)
if (matcher.find())
return matcher.group(1)
else {
println "NO MATCH FOUND"
return ""
}
}
Note 1: You can place this task in your app/build.gradle anywhere (I had placed it at the end).
Note 2: If you followed step 3 for build flavors, you can also add the root folder's sentry.properties in .gitignore. Since, it will be copied everytime you create a build.
Sentry should now be able to upload the proguard files for any release builds (or if you set minifyEnabled=true for any buildType).
In order to improve our QA workflow, we want to automatically build an APK file for each pull-request on Github so we can test it BEFORE the branch is merged. We already figured out how to build the file, but we are now wondering how to integrate this in our workflow.
It seems like most available Beta programs (e.g. Crashlytics Beta, Google Play) mostly focus on creating one beta version shortly before the release, but don't allow hosting multiple APKs in parallel.
Here's an example for our ideal workflow:
Developer finishes coding and creates a pull-request
Tests run
If tests are successful, an APK is built automatically and uploaded somewhere (that's the part we're trying to figure out)
QA takes a look at the pull-request and should be able to easily download the correct APK on their testing device
If there are no issues during QA, the pull-request is merged
The APK file is automatically deleted
We specifically don't want to test the APK after the pull-request has been merged, but instead test before so less bugs pop up in our develop branch.
Actually Crashlytics allow to have several versions of APK.
Ech version can have each own Version string and of course release notes, to help QA to find correct APK.
Point 3 from question can be described in that way:
CI configured to upload build to Crashlytics.
It can be achieved by gradle task:
gradle assembleRelease crashlyticsUploadDistributionRelease
It is really useful to have special build type (pullrequest) for this case. You can specify special distribution rules via distribution groups, notifications about builds and release notes.
build.gradle:
//example function for change log
def getLastGitCommitMessage() {
try {
"git log -1 --pretty=%B".execute().text.trim()
} catch (e) {
'Undefined message.'
}
}
android {
buildTypes {
...
pullrequest {
//invitation
ext.betaDistributionGroupAliases = "QA, devs"
// notification
ext.betaDistributionNotifications = true
// last commit message as release notes
ext.betaDistributionReleaseNotes = getLastGitCommitMessage()
}
}
}
In this case build and upload command will be like that:
gradle assemblePullrequest crashlyticsUploadDistributionPullrequest
There is a lot of ways to achieve it. But in my opinion the best way is to create next stage that will produce apk as artefact and later your QA team will be able to download apk on device and test it. As anaxad wrote you can also send apk file using mail and distribution list. But such solution will be more difficult, because you need to create task (e.g. using docker) that will send mail with apk.
Make your system the server.
After that at the time of generate APK, Give your server path.
If you do like this then every time your apk will update,
So you need to use one variable? Which will decide your apk deploy on your local server or not.
After finish your development, make it true and then your apk will copy on your local server. Then it is easily accessible to QA team.
Follow this question.
Some demo code.
debug {
applicationVariants.all { variant ->
variant.outputs.each { output ->
def apk = output.outputFile;
def newName;
newName = apk.name.replace("-" + variant.buildType.name, "")
.replace(project.name, name);
newName = newName.replace("-", "-" + version + "-" + milestone +
"-" + build + "-");
output.outputFile = new File(apk.parentFile, newName);
}
}
}
About Bintray-release plugin
I am using bintray-release to upload my library to maven.Its doc says how to use it:
Use the publish closure to set the info of your package:
publish {
userOrg = 'novoda'
groupId = 'com.novoda'
artifactId = 'bintray-release'
publishVersion = '0.3.4'
desc = 'Oh hi, this is a nice description for a project, right?'
website = 'https://github.com/novoda/bintray-release'
}
Finally, use the task bintrayUpload to publish
$ ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=false
In my case
Then I define my publish closure:
publish {
groupId = 'com.uniquestudio'
artifactId = 'parsingplayer'
publishVersion = '2.0.6'
website = 'https://github.com/TedaLIEz/ParsingPlayer'
Properties properties = new Properties()
InputStream inputStream = project.rootProject.file('local.properties').newDataInputStream() ;
properties.load( inputStream )
bintrayUser = properties.getProperty('bintrayUser')
bintrayKey = properties.getProperty('bintrayKey')
}
As you can see,out of safety I put bintrayUser and bintrayKey into local.properties.
My Question
First
I know I can put bintrayUser and bintrayKey in loacal.properties and gradle.properties.Is there any other way to store private data while I don't think is't suitable to store private data within current project ?
Second
Everything is ok but when I push my project to CI.I get error:
/home/travis/build/TedaLIEz/ParsingPlayer/local.properties (No such file or directory)
So I want to know How gradle task deal with extension objects,in my case,publish object.Is there any way to fix it?
First, I have to tell you that it is not recommended to ask two questions at once via StackOverflow, mainly because it may be hard to choose a correct answer, if two answers help you with the different questions you asked.
Anyhow, I'll try to answer both of your questions:
First
To use an additional properties file (local.properties in your case) is not a Gradle approach. It is in fact pure Java. You should only read properties on your own in very rare cases and never in a build script. If you really need an additional properties file, develop a Gradle plugin, which handles the file access.
Gradle automatically reads the gradle.properties file, but not only in the project directory, but also in the user-specific gradle home directory (e.g. C:\Users\*<User>*\.gradle). This is helpful to define private data, which won't find its way into version control, even if you forget to ignore the files manually. The defined data will be accessible to any project.
Second
Well, I assume the file local.properties does not exist, because you did neither put it under version control nor let your CI add it automatically. Where should the login data come from?
The solution is simple. Just add the required data to the CI user gradle home directories (e.g. /home/travis/.gradle) gradle.properties file. This way, you can also simply add access right management, by entering the login data of a CI user. Local builds will be published by your local user account (if allowed), CI builds by the CI system.
Appendix
Your question includes the Gradle specific term 'extension', but, to be honest, it got nothing to do with your question. It is correct, that most configuration in Gradle is done via so-called extension objects, that are added to the Project object, but it is an internal term, you do not need to understand it to fix this problem.
Edit: Comment answer
Now I can understand your confusion. Gradle distinguishes between the configuration phase and the execution phase. Nearly everything in your build script is executed during the configuration phase, only task actions (what a task does, e.g. copying, deleting ...), doFirst and doLast closures (so basically tasks) are executed during execution phase. If you define the list of tasks to be executed (via command line), it only affects the execution phase, but your configuration code will be executed at every single build, even if only one independent task is executed afterwards.
To solve this problem, follow the solution in the First block and add your private data to the user-specific Gradle directory gradle.properties file. It will be added to the project object and therefor, it will be accessible from the build file. But, since the file (or the data) does not exist on your CI, accessing it directly will raise an error when building on the CI. You can use the findProperty(propertyName) method as a fail-safe way to access the property value. If the property does not exist, it returns null (in the configuration phase), so no error occurs, as long as you don not execute the bintrayUpload task (which is not your goal on the CI).
I'd like to use crashlytics in our app, but I'm not allowed to upload it's proguard mapping file anywhere to the outside world (company policy). Is it possible to use Crashlytics but with obfuscated stacktraces?
In io.fabric plugin's docs I've found this option:
ext.enableCrashlytics = false
But it disables whole reporting, so that's not what I want.
I have added this at the end of app gradle file:
tasks.whenTaskAdded {task ->
if(task.name.toLowerCase().contains("crashlytics")) {
task.enabled = false
}
}
Build log:
> Task :app:crashlyticsStoreDeobsDebug SKIPPED
> Task :app:crashlyticsUploadDeobsDebug SKIPPED
Please note that this disables all crashlytics related tasks. Uploading proguard mapping file by default is some kind of misunderstanding. It is like uploading private key and its password. This file should only be stored in your vault. So I guess it is better to completely disable all their task by default :)
I am just wondering why this is not a big issue for developers.
They have everything planned ! ;-) According to this link : "Crashlytics automatically de-obfuscates stack traces for your reports", so you shouldn't have to worry about it.
Simply obfuscate your app with ProGuard, don't forget to update ProGuard rules to avoid unexpected crashes with release app, and it should be ok !
(help page is about Eclipse, but I used Crashlytics with Android Studio just some days ago, and it works fine too)
EDIT 1 : and according to the very end of this second link, Crashlytics automatically upload mapping file during build. It seems you aren't able to disable this.
EDIT 2 : maybe if you use Ant, you would be able to customize (at least a bit) build rules, thanks to crashlytics_build.xml and crashlytics_build_base.xml files. But I'm not used to Ant, and even there, when I read file, it seems the "mapping files auto upload" can't be disabled. :-/
try disable task 'crashlyticsUploadDeobs':
afterEvaluate {
for (Task task : project.tasks.matching { it.name.startsWith('crashlyticsUploadDeobs') }) {
task.enabled = false
}}
Add to app build Gradle file
firebaseCrashlytics {
mappingFileUploadEnabled false
}
https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=android
if you does not have internet connect at that time what will happened mapping will upload to crashlytics or not.
I have an Android-Library that I would like to publish on bintray.
So far so good, I am using the gradle-bintray-plugin 1.2 with the following configuration:
bintray {
user = properties.getProperty("bintray.user")
key = properties.getProperty("bintray.apikey")
configurations = ['archives']
pkg {
repo = "android" // thats how my maven repository is called on bintray
name = "mylibrary"
websiteUrl = "https://somewebsite" // replaced this, since the project should not matter
issueTrackerUrl = 'some issuetracker url'
vcsUrl = "some repository"
licenses = ["Apache-2.0"]
publish = true
version {
// the 2 functions here, are building the version number
name = versionNameBuild() // i.e. 1.0.0-SomeName
vcsTag = versionName() // 1.0.0
gpg {
// keys are uploaded
sign = true
}
}
}
}
The first issue I ran into, was that after I uploaded the library the Version number was "unspecified". Meaning, that the file was uploaded successfully, but it was called smth. like "mylibrary-unspecified.aar". I figured out that I had to additionally specify the project version number in gradle.
like this:
project.version = "1.0.1"
After that, this was working fine. Now I have only 2 Questions left:
I would expect my files to be uploaded like this:
$BINTRAYUSERNAME/$REPONAME/$PACKAGENAME/$VERSION/*.aar
But they are actually uploaded to smth like this:
$BINTRAYUSERNAME/$REPONAME/$PROJECT_FOLDERNAME_OF_ANDROID_STUDIO/$SUBDIRECTORY_OF_THE_LIBRARY/$VERSION/*.aar
Can I change this "path" somehow? Does it matter?
Which leads me to my next question.
How can I specify the maven group-type? I mean it's a maven repository right? So I should be able to set this up? Is this may be related to my first question?
Can I change this "path" somehow? Does it matter?
Yes and yes. This path changed, as soon as I used the group variable:
group = "my.awesome.group"
This solves the second Question as well. The path of the uploaded file is then:
$BINTRAYUSERNAME/$REPONAME/$PACKAGENAME/$GROUPPATH/$VERSION/*.aar
I recommend using for Android libraries the example script.