Jenkins Android building pipeline from different Git repositories - android

Hi, I would like to create a job in Jenkins where I would like to build one apk from 2 Android repositories.
I tried it with both Jenkinsfile and Gradle wrapper in the Jenkins GUI, but both are giving the same error at the same point, at the verification Gradle commands.
Since the code in the first repository is depending on the code from the 2nd, the structure was designed that they have to be sibling directories to reach each other.
What went wrong:
Could not determine the dependencies of task ':app:testStaging_gakUnitTest'.
Could not resolve all task dependencies for configuration ':app:staging_gakUnitTestRuntimeClasspath'.
Could not resolve project :ticketingcommons.
Required by:
project :app
Unable to find a matching variant of project :ticketingcommons:
First one is a specific application code repository.
Second one contains the commons for the specific apps to run tests, validation etc...
In the configuration I only set the source-code management and building process fields so far.
I have been trying with pipelines, freestyle projects, multibranch pipelines and nothing seemed to be working.
In the Jenkinsfile, I have the following code, which is supposed to do the same I was doing from the Jenkins GUI:
pipeline {
agent {
// Run on a build agent where we have the Android SDK installed
label 'master'
}
options {
// Stop the build early in case of compile or test failures
skipStagesAfterUnstable()
}
stages {
stage('Compile') {
steps {
// Compile the app and its dependencies
sh 'chmod +x gradlew'
sh './gradlew compileDebugSources'
}
}
stage('Unit test') {
steps {
// Compile and run the unit tests for the app and its dependencies
sh './gradlew test'
// Analyse the test results and update the build result as appropriate
junit 'app/build/test-results/**/*.xml'
}
}
stage('Build APK') {
steps {
// Finish building and packaging the APK
sh './gradlew assembleDev'
// Archive the APKs so that they can be downloaded from Jenkins
archiveArtifacts '**/*.apk'
}
}
stage('Stage Archive') {
steps {
//tell Jenkins to archive the apks
archiveArtifacts artifacts: 'app/build/outputs/apk/*.apk', fingerprint: true
}
}
stage('Static analysis') {
steps {
// Run Lint and analyse the results
sh './gradlew lintDebug'
androidLint pattern: '**/lint-results-*.xml'
}
}
}
post {
failure {
// Notify developer team of the failure
mail to: 'mymail#whynotworking.com', subject: 'Oops!', body: "Build ${env.BUILD_NUMBER} failed; ${env.BUILD_URL}"
}
}
}
I don't know how to make Jenkins have them as sibling directories after cloning them, so the app can see the commons and run the commands. Now it is failing at the tests, but every validation Gradle command makes it fail.

A simple solution could be to create two jobs and customize their workspace directory (So you can use the same directory for both jobs). In your first job just load the repository from git and in the second load the repository and run whatever commands you want.

Related

Is there any way to skip the assemble task and its dependencies in Android?

I am attempting to build a cache of APKs locally. The idea is that when you run any assemble type task, it will check locally for a previously built APK and move it into the build output folder.
I have made my own custom task that does this APK cache check. The signature is below:
tasks.register('apkCacheCheck') {
// checks for some criteria here
}
I have also made the assembleDebug task dependent on it:
tasks.whenTaskAdded{ task ->
if (task.name == "assembleDebug") {
task.dependsOn "apkCacheCheck"
task.enabled = foo // some output of apkCacheCheck
}
}
Problem:
When running assembleDebug to build an Android app, it will go up the graph and execute all its dependent tasks. However, I wanted to write a check that prevents the app from building anything until the check is finished. This way, we can save time by installing the cached APK rather than re-building it from scratch.
Questions:
Is there a way to stop all of the build tasks if said condition is met?
Is this a valid use case of Gradle or should I be looking towards running this as a Bash script?

Isolating test APK build

I have a situation where in an Android project with instrumentation tests I have all of the production code precompiled and ready to be installed as an .apk (a React Native environment).
Whenever I run instrumentation tests, I initially build the AndroidTest .apk using Gradle by running:
./gradlew assembleDebugAndroidTest -DtestBuildType=debug
(i.e. in a pretty standard way).
Trouble is that despite explicitly specifying only the xxxAndroidTest task, all of the production code assembly Gradle tasks are run as well. This is an extreme time waster to me since - as I explained, the production apk is already there, and thus code compilation (and packaging, signing, etc.) is scarce.
In essence, I have no dependency in production code from the instrumentation code -- even the ActivityTestRule I use is created dynamically and isn't directly bound to my main activity:
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(launchIntent, 0);
Class<?> activityClass = Class.forName(resolveInfo.activityInfo.name);
ActivityTestRule<?> activityTestRule = new ActivityTestRule(activityClass, false, false);
Question is: How can I isolate / restrict Gradle's work so it would only include test-related tasks? I even tried inspecting the tasks tree using this Gradle plugin, but couldn't find a clear place to "cut the tree" down.
Well so far I've come up with this (heuristic) solution, that does 2 things:
I noticed that most of the time that goes to waste is due to sub-projects that are not needed for the job. Therefore, the solution provides an easy way to exclude implementations from test building.
Out of the tasks remaining in the list, still - the plugin iteratively force-disables tasks that are not related but run nonetheless.
It boils down to this helper Gradle script:
// turbo-test-apk.gradle
def isEnabled = System.getProperty('TURBO_TEST_APK') != null
project.ext.dependenciesExcludeTest = { depsClosure ->
if (!isEnabled) {
dependencies(depsClosure)
}
}
gradle.taskGraph.whenReady { graph ->
if (isEnabled) {
def disabledTasks = new ArrayList<Task>(graph.allTasks.size())
[/.*JsAndAssets.*/, /package.*Release/, /package.*Debug/, /compile.*/, /.*[Pp]roguard.*/, /.*[Nn]ew[Rr]elic.*/, /.*AndroidTest.*/].forEach { regex ->
graph.allTasks.findAll { it.name ==~ regex }.forEach({ task ->
disabledTasks.add(task)
task.enabled = false
})
}
graph.allTasks.findAll { it.name ==~ /.*AndroidTest.*/ }.forEach({ task ->
task.enabled = true
})
println '--- Turbo test build: task scanning ---'
disabledTasks.forEach { task ->
if (!task.enabled) {
println 'Force-skipping ' + task
}
}
println '---------------------------------------'
}
}
Namely, the dependenciesExcludeTest enabled the exclusion of unwanted subprojects, and the task-graph-ready callback does the disabling. NOTE that the regex list is custom made, and is not generic. It makes sense for my project as react native projects have a heavy-weight JS-bundling tasks called bundleJsAndAssets, and I also have new relic installed. Nevertheless, this can be easily tailored to any project.
Also, the app.gradle looks something like this:
apply plugin: 'com.android.application'
apply from: './turbo-test-apk.gradle'
dependencies {
implementation "org.jetbrains.kotlin:$kotlin_stdlib:$kotlinVersion"
implementation "com.android.support:support-v4:$supportLibraryVersion"
implementation "com.android.support:appcompat-v7:$supportLibraryVersion"
// etc.
}
// These will be excluded when executing test-only mode
dependenciesExcludeTest {
implementation project(':#react-native-community_async-storage')
implementation project(':any-unneeded-sub-project')
}
So when gradle is run like this (i.e. with a custom TURBO_TEST_APK property):
./gradlew assembleDebugAndroidTest -DtestBuildType=debug -DTURBO_TEST_APK
the script will apply its work and reduce the overall build time.
This solution isn't optimal: tricky to maintain, doesn't omit all of the unnecessary work. I'd be very happy to see more effective solutions.

Android Robolectric Exception

I am facing a problem with Robolectric library. I am writing unit tests using Robolectric, it's working locally but when I merge my code, it crashes on pipeline(remotely).
I am using 'org.robolectric:robolectric:4.0.2'
It fails by only adding this line to my test class: #RunWith(RobolectricTestRunner.class)
And the exception is:
FAILED
org.apache.tools.ant.BuildException
Caused by: org.apache.maven.artifact.resolver.MultipleArtifactsNotFoundException
I had the same issue: Robolectric was working fine locally, but once pushed to Jenkins the gradle task to execute the tests fails.
You can execute the gradle task to run the tests with -i -d flags to see more debug output.
./gradlew -i -d test
For me this revealed that Jenkins was unable to download Robolectric dependencies:
13:58:43 13:58:42.904 [DEBUG] [TestEventLogger] com.my.package.Test > my_test_case STANDARD_ERROR
13:58:43 13:58:42.904 [DEBUG] [TestEventLogger] Downloading: org/robolectric/android-all/9-robolectric-4913185-2/android-all-9-robolectric-4913185-2.jar from repository sonatype at https://oss.sonatype.org/content/groups/public/
I could solve it by telling gradle to use our corporate proxy when running on Jenkins. One way to achieve this would be to add the following to your gradle.properties:
systemProp.http.proxyHost=http://proxy.host
systemProp.http.proxyPort=3128
systemProp.https.proxyHost=http://proxy.host
systemProp.https.proxyPort=3128
---- EDIT ----
Actually I found a cleaner solution for my use case then configuring a proxy: Robolectric offers a way to configure the repository it uses during runtime (see http://robolectric.org/configuring/). This way I was able to tell it to use our corporate repository.
android {
testOptions {
unitTests.all {
systemProperty 'robolectric.dependency.repo.url', 'https://local-mirror/repo'
systemProperty 'robolectric.dependency.repo.id', 'local'
}
}
}
In my case the issue was (./gradlew -i -d testDebug printed it):
Caused by:
org.apache.maven.artifact.resolver.MultipleArtifactsNotFoundException: Missing:
----------
1) org.robolectric:android-all:jar:10-robolectric-5803371
...
Robolectric tried to fetch a dependency from the remote repository, but it couldn't.
As the output suggests, I have:
Manually downloaded org.robolectric:android-all:jar:10-robolectric-5803371 jar from the Maven repository.
Located it in /home/user/jars
Installed Maven on my machine: sudo apt install maven
Linked the jar with Robolectic: mvn install:install-file -DgroupId=org.robolectric -DartifactId=android-all -Dversion=10-robolectric-5803371 -Dpackaging=jar -Dfile=/home/user/jars/android-all-10-robolectric-5803371.jar
Now ./gradlew testDebug works fine.
Also check the issue on Github, maybe there will be some more useful information for you.
As #Christian.D said, Robolectric tries to download dependencies from external repository, but we need that it uses internal one. Below the fix:
1) Create a custom RobolectricTestRunner:
public class CustomRobolectricRunner extends RobolectricTestRunner {
public CustomRobolectricRunner(Class<?> testClass) throws InitializationError {
super(testClass);
}
static {
RoboSettings.setMavenRepositoryId("my-nexus");
RoboSettings.setMavenRepositoryUrl("your_custom_url");
}
}
2) Annotate your test class:
#Config(manifest = Config.NONE)
#RunWith(CustomRobolectricRunner.class)
Good coding!

Pipeline script to upload build(apk) to hockeyApp

I am using Jenkins to build my android app. i added "Jenkinsfile" in my repo and my current pipeline script looks like this and it is working fine.
node
{
try {
stage("build.clone")
{
checkout scm
}
stage("build.clean")
{
sh "./gradlew clean"
}
stage("build.package")
{
sh "./gradlew assembleDebug"
}
} catch (error) {
throw error
}
}
Now i need to upload my apk to hockeyapp. HockeyApp's Jenkins plugin is installed. And they have made it compatible with pipeline in v 1.2.2
I have googled a lot but found not much help. Kindly guide me or point me to how I can use pipeline script to upload apk to hockeyapp.
Note: I have already created an app on hockey app and I have app token and app id. Also i know curl command is available but i want to use hockeyapp plugin with pipeline
You should be able to get the syntax for just about any Pipeline step via the snippet generator built in to Jenkins.
Doing so in my Jenkins install gives me something like this (horrifying) syntax:
step([$class: 'HockeyappRecorder',
applications: [[downloadAllowed: false, mandatory: false,
notifyTeam: false, releaseNotesMethod: [$class: 'NoReleaseNotes'],
uploadMethod: [$class: 'AppCreation', publicPage: false]]],
debugMode: false, failGracefully: false])

Intellij not recognizing directory as tests

I added another source set, and a task of type test that uses that source set. I can execute the task and run the tests fine.
However, Intellij doesn't consider this src/newTests/java as tests. If I 'Mark directory as test sources' in the UI, it will forget it he next time build.gradle is synced. How do I make it remember this?
Try this:
android {
sourceSets {
test {
java.srcDir file('src/newTest/java')
}
}
}
Result:

Categories

Resources