0

I'm using Jenkins to set up CI for an Android app. I'm trying to configure the Android SDK as a Jenkins Global Tool, using the Custom Tools Plugin. Here's the installation shell script I configure in Jenkins>Global Tool Configuration.

printf "\nInstalling Android SDK as a Jenkins Custom Tool\n"

pwd
ls -la

if [ ! -d build-tools ];
    then
        printf "There's no dir by the name 'build-tools' on this agent yet. Will proceed to install the Android SDK.\n"

        printf "Downloading the Android SDK tools for Linux\n"
        curl -o android.zip https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
        printf "Download successful\n"

        printf "Unzipping downloaded Android SDK tools\n"
        unzip -o -z android.zip
        printf "Done unzipping\n"

        printf "Removing downloaded zip file\n"
        rm android.zip
        printf "Done removing\n"

        printf "Installing Android Build Tools 23.0.1\n"
        echo "y" | tools/bin/sdkmanager "build-tools;23.0.1"
        printf "Done installing Android Build Tools 23.0.1\n"
    else
        printf "There's already a dir by the name 'build-tools' on this agent. Aborting installation of the Android SDK.\n"
fi
printf "Done installing Android SDK as a Jenkins Custom Tool\n\n"

The job I'm currently trying to set up downloads an unsigned apk from another location and attempts to sign it.

Here's the Jenkinsfile:

properties([
    parameters([
        [
            $class: 'CredentialsParameterDefinition',
            name: 'ANDROID_KEYSTORE_FILE',
            credentialType: 'com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl',
            defaultValue: params.ANDROID_KEYSTORE_FILE?:'',
            description: 'The Android keystore file in p12 format.',
            required: false
        ],
        string(
            name: 'ANDROID_SIGN_KEY_ALIAS',
            defaultValue: params.ANDROID_SIGN_KEY_ALIAS?:'',
            description: 'The alias of the signing key inside your keystore. If your keystore only has one key, you can leave this blank.',
        ),
    ])
])

stage('Binary sign'){
    node('standard'){
        def pwd = pwd()
        echo(pwd)
        sh('curl -o foo-unsigned.apk https://some/place/in/my/intranet/FooApp_12.apk')
        sh('ls -la')

        def androidHome = tool('android-sdk')

        //Let's sign and archive the unsigned and signed apk files
        withEnv([
            "ANDROID_HOME=${androidHome}"]) {

            signAndroidApks (
                keyStoreId: params.ANDROID_KEYSTORE_FILE,
                keyAlias: params.ANDROID_SIGN_KEY_ALIAS,
                apksToSign: "**/*-unsigned.apk",
                androidHome: env.ANDROID_HOME,
                archiveSignedApks: true,
                archiveUnsignedApks: true
            )   
        }
    }
}

You can see I'm using the signAndroidApks step. The problem is when it executes I get this error:

[android-sign-test] $ echo "resolving effective environment"
[SignApksBuilder] zipalign ANDROID_HOME explicitly set to /Jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/android
[SignApksBuilder] found zipalign in Android SDK's latest build tools: /Jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/android/build-tools/23.0.1/zipalign
[SignApksBuilder] /Jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/android/build-tools/23.0.1/zipalign -f -p 4 /Jenkins/workspace/AFS-2.0/android-sign-test/foo-unsigned.apk /Jenkins/workspace/AFS-2.0/android-sign-test/SignApksBuilder-out/zipalign/aligned-foo-unsigned-6084722845076851930.apk
[android-sign-test] $ /Jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/android/build-tools/23.0.1/zipalign -f -p 4 /Jenkins/workspace/AFS-2.0/android-sign-test/foo-unsigned.apk /Jenkins/workspace/AFS-2.0/android-sign-test/SignApksBuilder-out/zipalign/aligned-foo-unsigned-6084722845076851930.apk
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // stage
[Pipeline] End of Pipeline
java.io.IOException: error=2, No such file or directory
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
    at java.lang.ProcessImpl.start(ProcessImpl.java:134)
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
    at hudson.Proc$LocalProc.<init>(Proc.java:249)
    at hudson.Proc$LocalProc.<init>(Proc.java:218)
    at hudson.Launcher$LocalLauncher.launch(Launcher.java:930)
    at hudson.Launcher$ProcStarter.start(Launcher.java:450)
    at hudson.Launcher$RemoteLaunchCallable.call(Launcher.java:1299)
    at hudson.Launcher$RemoteLaunchCallable.call(Launcher.java:1260)
    at hudson.remoting.UserRequest.perform(UserRequest.java:153)
    at hudson.remoting.UserRequest.perform(UserRequest.java:50)
    at hudson.remoting.Request$2.run(Request.java:336)
    at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:68)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at hudson.remoting.Engine$1$1.run(Engine.java:94)
    at java.lang.Thread.run(Thread.java:748)
Caused: java.io.IOException: Cannot run program "/Jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/android/build-tools/23.0.1/zipalign" (in directory "/Jenkins/workspace/AFS-2.0/android-sign-test"): error=2, No such file or directory
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
    at hudson.Proc$LocalProc.<init>(Proc.java:249)
    at hudson.Proc$LocalProc.<init>(Proc.java:218)
    at hudson.Launcher$LocalLauncher.launch(Launcher.java:930)
    at hudson.Launcher$ProcStarter.start(Launcher.java:450)
    at hudson.Launcher$RemoteLaunchCallable.call(Launcher.java:1299)
    at hudson.Launcher$RemoteLaunchCallable.call(Launcher.java:1260)
    at hudson.remoting.UserRequest.perform(UserRequest.java:153)
    at hudson.remoting.UserRequest.perform(UserRequest.java:50)
    at hudson.remoting.Request$2.run(Request.java:336)
    at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:68)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at hudson.remoting.Engine$1$1.run(Engine.java:94)
    at java.lang.Thread.run(Thread.java:748)
    at ......remote call to JNLP4-connect connection from ip-172-16-2-29.eu-west-1.compute.internal/172.16.2.29:44916(Native Method)
    at hudson.remoting.Channel.attachCallSiteStackTrace(Channel.java:1545)
    at hudson.remoting.UserResponse.retrieve(UserRequest.java:253)
    at hudson.remoting.Channel.call(Channel.java:830)
    at hudson.Launcher$RemoteLauncher.launch(Launcher.java:1053)
    at hudson.Launcher$ProcStarter.start(Launcher.java:450)
    at hudson.Launcher$ProcStarter.join(Launcher.java:461)
    at org.jenkinsci.plugins.androidsigning.SignApksBuilder.perform(SignApksBuilder.java:332)
    at org.jenkinsci.plugins.androidsigning.SignApksStep$SignApksStepExecution.run(SignApksStep.java:174)
    at org.jenkinsci.plugins.androidsigning.SignApksStep$SignApksStepExecution.run(SignApksStep.java:126)
    at org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution$1$1.call(AbstractSynchronousNonBlockingStepExecution.java:47)
    at hudson.security.ACL.impersonate(ACL.java:260)
    at org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution$1.run(AbstractSynchronousNonBlockingStepExecution.java:44)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Finished: FAILURE

You can see the stack says No such file or directory, but I've checked and both the zipalign executable and the unsigned apk are where they're supposed to be. Has anyone come across this issue before? Any ideas?

Mig82
  • 4,856
  • 4
  • 40
  • 63

1 Answers1

0

I found a solution to the problem. First I tried a simpler script to see whether I could actually invoke the zipalign program:

stage('Binary sign'){
    node('standard'){

        sh("/Jenkins/tools/com.cloudbees.jenkins.plugins.customtools.CustomTool/android-sdk/build-tools/23.0.1/zipalign")
    }
}

This caused the following error:

zipalign: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory

After a little research I found out this is related to lack of support for 32 bit executables on certain Linux distros. My build agent was running AWS Linux which apparently doesn't come with out-of-the-box support for 32 bit binaries. The Android SDK build tools are all 32 bit executables. So after some research I tried installing the libraries to support 32 bit binaries:

yum install glibc.i686

Executing the same script above then produced this error:

zipalign: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory

Then I also tried:

yum install libzip.i686

And this finally solved the issue. The script above now renders the normal response zipalign should when invoked with no arguments.

Zip alignment utility
Copyright (C) 2009 The Android Open Source Project

Usage: zipalign [-f] [-p] [-v] [-z] <align> infile.zip outfile.zip
       zipalign -c [-v] <align> infile.zip

  <align>: alignment in bytes, e.g. '4' provides 32-bit alignment
  -c: check alignment only (does not modify file)
  -f: overwrite existing outfile.zip
  -p: page align stored shared object files
  -v: verbose output
  -z: recompress using Zopfli

My original script now performs fine.

Mig82
  • 4,856
  • 4
  • 40
  • 63