riga dev day - automated android continuous integration
TRANSCRIPT
AUTOMATED ANDROID CONTINUOUS INTEGRATION: HOW HARD CAN IT BE?@NICOLAS_FRANKEL
@nicolas_frankel #springboot 2
ME, MYSELF AND I Backend Java Developer• As consultant
@nicolas_frankel #springboot 3
HYBRIS, AN SAP COMPANY
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 CONFIGURATIONNo central registry• Stored in XML• In each job’s folder
WHAT WE DIDAnalyzed the config of an existing jobDivided it into snippetsCreated Puppet template for eachAssembled and filled in snippets through classes
WHAT WE DIDCreation of a Jenkins DSL in Puppet
fail!
WHY FAIL?Time-consumingError-proneFragile• Implementation details
Reinventing the wheel
ALTERNATIVESJenkins Job Builder• Part of OpenStack•DSL that calls Jenkins API
• YAML configuration
SOFTWARE INSTALL AUTOMATION
DONE!
DEPENDENCIES
THE CHALLENGE
ISSUES
1. Gradle Wrapper2. Robolectric3. Android
platforms/extra/add-ons
JENKINS GRADLE PLUGIN
GRADLE ISSUE./gradlew xxx
GRADLE WRAPPERMade of:•A JAR•A property
Downloads the required version from the Internet
GRADLE-WRAPPER.PROPERTIESdistributionUrl=https\://services.gradle.org/distributions/gradle-2.3-bin.zip
THE ROOT ISSUEThe proxy…
REQUIREMENTArtifactoryNexusSimple Web Server?
UPDATED GRADLE-WRAPPER.PROPERTIESdistributionUrl=https\://intranet.mydomain.org/org/gradle/gradle-2.3-bin.zip
GRADLE ISSUE./gradlew xxx
DONE!
ROBOLECTRIC ISSUE./gradlew test
AN ERROR!?Error:Could not HEAD 'http://repo1.maven.org/maven2/org/robolectric/robolectric-gradle-plugin/0.12.0/robolectric-gradle-plugin-0.12.0.jar'. Received status code 407 from server: AuthorizedOnly
GRADLE.PROPERTIESsystemProp.http.proxyHost=mydomain.orgsystemProp.http.proxyPort=8080systemProp.http.proxyUser=usersystemProp.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/nJgHtdbkpi8
Y U ROBOLECTRIC?Uses a custom class loaderEither:•Download dependencies
beforehand•Override method• Pass through proxy
YOUR OWN TEST RUNNER@Overrideprotected 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 RESOLVERpublic 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
DONE!
ANDROID SDK ISSUEandroid update sdk
ANDROID SDK ISSUEandroid update sdk -u
Non interactive mode
2 ISSUESSet the proxyAccept license agreements
ANDROID SDK --HELPAction "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 parametersBut no authentication…
SOLUTIONS (THANKS GOOGLE FOR NOTHING)
Environments variablesSpecific credential fileLocal proxy
A WORKING SOLUTION: EXPECTLinux binaryOriginally for testing purposeScript input in regard to output
(VERY) SIMPLE EXPECT SCRIPT#!/usr/bin/expecteval spawn jot -r 1 0 1expect { "0" { puts "zero" } "1" { puts "one" }}
THE “REAL” SCRIPT#!/bin/bashexpect -d -c 'log_file /var/log/update-android.logset timeout -1spawn <%= @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 "y\r" } }}'
Handles the proxy
Handles license agreements
ANDROID SDK ISSUEandroid update sdk -u
DONE!
ANDROID CONTINUOUS INTEGRATION?Not mature• Lags behind “standard” Java
So time-consuming…• But possible!
48
Q&A
@nicolas_frankel
http://blog.frankel.ch/@nicolas_frankel http://frankel.in/