Embedding Perl on Android

The choice of using Perl in an Android project may not seem like an obvious one, indeed its not one that I would often recommend. However, with one of the largest repositories of free code in the world available through CPAN, the Comprehensive Perl Archive Network, the choice can come down to leveraging an off-the-shelf project. The one that got me interested was ExifTool by Phil Harvey that is by far the most powerful EXIF reader and writer out there. Why re-invent the wheel?

The Scripting Layer for Android (SL4A) project attempts to make, amongst other things, Perl scripts executable on Android. With little support Perl no longer functions on modern devices so I set out to see how I could make Perl executable myself.

With Perl 5.20 cross compiling for Android works out of the box, sort of. The one caveat is it doesn’t currently generate a position independent executable (PIE) which is needed to run on Android 5.0. PIE support was added in Android 4.1.

These instructions are based on David Farrells’ Perl Tricks article for compilation.

Compiling Perl for ARM

Follow these instructions to compile Perl for ARM. There are footnotes below to compile for x86 and MIPS.

  • Download and install the Android NDK.
export ANDROID_NDK=/Applications/android-ndk-r10d
export TARGET_ARCH=arm-linux-androideabi
export ANDROID_TOOLCHAIN=/tmp/toolchain-arm-linux-androideabi
export SYSROOT=$ANDROID_TOOLCHAIN/sysroot
export TARGETDIR=/mnt/asec/perl
export GCC=$TARGET_ARCH-gcc
export PATH=$PATH:$ANDROID_NDK/toolchains/$TARGET_ARCH-4.9/prebuilt/darwin-x86_64/bin
  • Generate the toolchain and configure. These instructions are for Mac OS X so you may need to alter darwin-x86_64 to match your host system — look in $ANDROID_NDK/toolchains/$TARGET_ARCH-4.9/prebuild to confirm the required value. Also ensure targethost matches the id of your emulator.
$ANDROID_NDK/build/tools/make-standalone-toolchain.sh — platform=android-16 — install-dir=$ANDROID_TOOLCHAIN — system=darwin-x86_64 — toolchain=$TARGET_ARCH-4.9
./Configure -des -Dusedevel -Dusecrosscompile -Dtargetrun=adb -Dcc=$GCC -Dsysroot=$SYSROOT -Dtargetdir=$TARGETDIR -Dtargethost=emulator-5554
  • Modify config.sh to enable PIE support. Append “-fPIE” to ccflags and “-fPIE -pie” to ldflags. Then rerun configure to ensure this is applied.
./Configure -der
  • Compile :-) make test deploys Perl to the device however takes a long time to finish — I never ran this myself and is included for completeness.
make
make test

If all went well at the end of this you will have a Perl binary compiled for ARM. This can be executed through the shell at /mnt/asec/perl however the binary will disappear on restart of the emulator.

Executing Perl in an APK

As Perl is a binary and not a shared library you have to copy it and make it executable. The typical practice for this is to copy the binary to inside the applications getFilesDir() and make it executable. The following code can be used to set the file permissions to a mode of 0755 (the leading 0 is important as this is an octal number).

public static int chmod(File path, int mode) throws Exception {
Class<?> fileUtils = Class.forName("android.os.FileUtils");
Method setPermissions = fileUtils.getMethod("setPermissions", String.class, int.class, int.class, int.class);
return (Integer) setPermissions.invoke(null, path.getAbsolutePath(), mode, -1, -1);
}

For each processor architecture you need to include a separate binary. I bundled Perl in 3 different zip files, one for each of ARM, x86 and MIPS. Then using the Build class determined which architecture to unzip.

private String[] supportedAbis() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return Build.SUPPORTED_ABIS;
}

//noinspection deprecation
return new String[]{Build.CPU_ABI, Build.CPU_ABI2};
}

Perl comes with standard modules found in the lib directory. These need to be packaged up with the Perl binary as they define core things such as strict. This comes in at about 40MB however can be cut down to a smaller subset based on your requirements. The bare minimum for me is Carp, Exporter, strict, vars and warnings. As with the Perl binary I copied these into a folder within getFilesDir() as Perl references these.

Once Perl and its libs are on the device its a simple process to execute your first script. The code below executes print ”Hello, Android!”;. Ensure the path to the Perl binary and the lib directory point to where you copy them to.

List<String> command = new ArrayList<>();
command.add(context.getFilesDir().getAbsolutePath() + "/perl/perl");
command.add("-e");
command.add("print \"Hello, Android!\";");
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.environment().put("PERL5LIB", context.getFilesDir().getAbsolutePath() + "/perl/lib/");
Process process = processBuilder.start();
process.waitFor();

process.destroy();

Reading from process.getInputStream() and process.getErrorStream() allows you to see the output of the Perl process.

Executing a script is as easy as changing the command to point to your script, scriptFile.getAbsolutePath(). The working directory for the process can be set using processBuilder.directory(scriptFile.getParent());.

Compiling for x86

To compile for x86 follow the instructions above substituting the following environment variables:

export TARGET_ARCH=x86
export ANDROID_TOOLCHAIN=/tmp/toolchain-x86
export GCC=i686-linux-android-gcc

Compiling for MIPS

To compile for MIPS follow the instructions above substituting the following environment variables:

export TARGET_ARCH=mipsel-linux-android
export ANDROID_TOOLCHAIN=/tmp/toolchain-mips

Matt Dolan has been eating doughnuts and developing with Android since the dark days of v1.6.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store