riga dev day - automated android continuous integration

Post on 07-Apr-2017

12.559 Views

Category:

Software

3 Downloads

Preview:

Click to see full reader

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/

top related