KEMBAR78
Riga Dev Day - Automated Android Continuous Integration | PPTX
AUTOMATED ANDROID
CONTINUOUS INTEGRATION:
HOW HARD CAN IT BE?
@NICOLAS_FRANKEL
ME, MYSELF AND I
@nicolas_frankel #springboot
2
 Backend Java Developer
• As consultant
HYBRIS, AN SAP COMPANY
3
@nicolas_frankel #springboot
THE EXISTING ORGANIZATION
Software Factory
Mobile
Dev Team
THE REAL ORGANIZATION
Software Factory
Mobile
Dev Team
THE EXISTING SITUATION
SOFTWARE INSTALL AUTOMATION
SOFTWARE INSTALL AUTOMATION
JENKINS JOB CONFIGURATION
 No central registry
• Stored in XML
• In each job’s folder
WHAT WE DID
Analyzed the config of an
existing job
Divided it into snippets
Created Puppet template for
each
Assembled and filled in
snippets through classes
WHAT WE DID
Creation of a Jenkins DSL in
Puppet
WHY FAIL?
Time-consuming
Error-prone
Fragile
• Implementation details
Reinventing the wheel
ALTERNATIVES
Jenkins Job Builder
• Part of OpenStack
• DSL that calls Jenkins API
• YAML configuration
SOFTWARE INSTALL AUTOMATION
DEPENDENCIES
THE CHALLENGE
ISSUES
1. Gradle Wrapper
2. Robolectric
3. Android
platforms/extra/add-ons
JENKINS GRADLE PLUGIN
GRADLE ISSUE
./gradlew xxx
GRADLE WRAPPER
Made of:
• A JAR
• A property
Downloads the required
version from the Internet
GRADLE-WRAPPER.PROPERTIES
distributionUrl=
https://services.gradle.org/distributions/g
radle-2.3-bin.zip
THE ROOT ISSUE
The proxy…
REQUIREMENT
Artifactory
Nexus
Simple Web Server?
UPDATED GRADLE-WRAPPER.PROPERTIES
distributionUrl=
https://intranet.mydomain.org/org/gradle/gr
adle-2.3-bin.zip
GRADLE ISSUE
./gradlew xxx
ROBOLECTRIC ISSUE
./gradlew test
AN ERROR!?
Error:Could not HEAD
'http://repo1.maven.org/maven2/org/robolectr
ic/robolectric-gradle-
plugin/0.12.0/robolectric-gradle-plugin-
0.12.0.jar'. Received status code 407 from
server: AuthorizedOnly
GRADLE.PROPERTIES
systemProp.http.proxyHost=mydomain.org
systemProp.http.proxyPort=8080
systemProp.http.proxyUser=user
systemProp.http.proxyPassword=password
WTF!?... SAME ERROR
Y U ROBOLECTRIC?
public class RobolectricTestRunner {
protected DependencyResolver getJarResolver() {
if (dependencyResolver == null) {
if (Boolean.getBoolean("robolectric.offline")) {
String dependencyDir = System.getProperty(
"robolectric.dependency.dir", ".");
dependencyResolver = new LocalDependencyResolver(
new File(dependencyDir));
} else {
File cacheDir = new File(new File(
System.getProperty("java.io.tmpdir")), "robolectric");
if (cacheDir.exists() || cacheDir.mkdir()) {
Logger.info(
"Dependency cache location: %s", cacheDir.getAbsolutePath());
dependencyResolver = new CachedDependencyResolver(
new MavenDependencyResolver(), cacheDir, 60 * 60 * 24 * 1000);
} else {
dependencyResolver = new MavenDependencyResolver();
}
}
}
}
Y U ROBOLECTRIC?
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.robolectric</groupId>
<artifactId>robolectric-parent</artifactId>
<version>3.1-SNAPSHOT</version>
</parent>
<artifactId>robolectric</artifactId>
<dependencies>
...
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-ant-tasks</artifactId>
<version>2.1.3</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
Y U ROBOLECTRIC?
“Robolectric downloads a version of Android at runtime
that's built specifically for running tests. Each API level is
~40mb, so producing a single jar file that includes everything
would be prohibitive. All of the artifacts are available on
maven central under the coordinates:
org.robolectric:android-all. You can grab the API levels from
maven central and use them with offline mode.”
https://groups.google.com/forum/#!topic/robolectric/nJgHt
dbkpi8
Y U ROBOLECTRIC?
Uses a custom class loader
Either:
• Download dependencies
beforehand
• Override method
• Pass through proxy 
YOUR OWN TEST RUNNER
@Override
protected DependencyResolver getJarResolver() {
if (this.dependencyResolver == null) {
if (Boolean.getBoolean("robolectric.offline")) {
String cacheDir = System.getProperty(
"robolectric.dependency.dir", ".");
this.dependencyResolver =
new LocalDependencyResolver(
new File(cacheDir));
} else {
this.dependencyResolver = new MyResolver();
}
}
return this.dependencyResolver;
}
YOUR OWN RESOLVER
public class MyResolver
extends MavenDependencyResolver {
@Override
protected void configureMaven(
DependenciesTask task) {
RemoteRepository remoteRepository =
new RemoteRepository();
remoteRepository.setId("My Maven repo");
remoteRepository.setUrl(BuildConfig.NEXUS_URL);
task.addConfiguredRemoteRepository(
remoteRepository);
}
}
ROBOLECTRIC ISSUE
./gradlew test
ANDROID SDK ISSUE
android update sdk
ANDROID SDK ISSUE
android update sdk -u
Non interactive mode
2 ISSUES
Set the proxy
Accept license agreements
ANDROID SDK --HELP
Action "update sdk":
Updates the SDK by suggesting new platforms to install if available.
Options:
-f --force : Forces replacement of a package or its parts, even if
something has been modified.
-n --dry-mode : Simulates the update but does not download or install
anything.
--proxy-host: HTTP/HTTPS proxy host (overrides settings if defined)
-s --no-https : Uses HTTP instead of HTTPS (the default) for downloads.
-t --filter : A filter that limits the update to the specified types of
packages in the form of a comma-separated list of
[platform, system-image, tool, platform-tool, doc, sample,
source]. This also accepts the identifiers returned by
'list sdk --extended'.
-u --no-ui : Updates from command-line (does not display the GUI)
--proxy-port: HTTP/HTTPS proxy port (overrides settings if defined)
-p --obsolete : Deprecated. Please use --all instead.
-a --all : Includes all packages (such as obsolete and non-dependent
ones.)
WTF?!!?
Accepts proxy parameters
But no authentication…
SOLUTIONS (THANKS GOOGLE FOR NOTHING)
Environments variables
Specific credential file
Local proxy 
A WORKING SOLUTION: EXPECT
Linux binary
Originally for testing
purpose
Script input in regard to
output
(VERY) SIMPLE EXPECT SCRIPT
#!/usr/bin/expect
eval spawn jot -r 1 0 1
expect {
"0" {
puts "zero"
}
"1" {
puts "one"
}
}
THE “REAL” SCRIPT
#!/bin/bash
expect -d -c '
log_file /var/log/update-android.log
set timeout -1
spawn <%= @android_sdk_dir %>/tools/android -v update sdk -a -u -f --proxy-host
<%= @proxy_host %> --proxy-port <%= @proxy_port %> -t <%= @joined_packages %>
while {1} {
expect {
"Login:" {
send "<%= @proxy_user %>r"
}
"Password:" {
send "<%= @proxy_password %>r"
}
"Workstation:" {
send "r"
}
"Domain:" {
send "r"
}
-re ".*[y/n]:" {
send "yr"
}
}
}'
Handles the proxy
Handles license agreements
ANDROID SDK ISSUE
android update sdk -u
ANDROID CONTINUOUS INTEGRATION?
Not mature
• Lags behind “standard” Java
So time-consuming…
• But possible!
Q&A
@nicolas_frankel
48
http://blog.frankel.ch/
@nicolas_frankel
http://frankel.in/

Riga Dev Day - Automated Android Continuous Integration