DEV Community

Cover image for Compiling Perl for Android
Juan Carlos Corona Romero
Juan Carlos Corona Romero

Posted on

Compiling Perl for Android

The Why

I'm building a photo review app for Android, and I want to parse the full EXIF metadata in Canon RAW CR3 files.

I found no better tool than ExifTool by Phil Harvey. It works amazingly for this format and more!

Good news: It's platform-independent!
Not so good news: It needs Perl 😰

The How

After some digging, I found this article — Embedding Perl on Android — which covers compiling and using Perl on Android, and funny enough it also mentions using this for ExifTool 😆

I reviewed the prescribed steps, but found it to be using a now obsolete method for compilation.

🏁 Let's begin doing this the 2023 way!

Sorry Windows devs, the following assumes a UNIX-like setup, and I only tested this using macOS. Maybe WSL will work?

Here's the summary of my derived approach:

  1. Download and install the Android NDK
  2. Download the Perl source code
  3. Set up an ARM based emulator
  4. Prepare the environment for cross-compilation
  5. Compile Perl with Configure and make
  6. Extract the perl binary and lib/ outputs
  7. Try things out using ADB

1. Download and install the Android NDK

The easiest way I found to do this is to download the NDK (Side by side) option under the SDK Tools section of the Android SDK Manager (which comes with Android Studio).

It will be installed into your Android SDK Location under an ndk/<version> sub directory.

2. Download the Perl source code

I downloaded the latest source from the same location as the article:
Comprehensive Perl Archive Network - Perl Source

curl -O https://www.cpan.org/src/5.0/perl-5.36.1.tar.gz
Enter fullscreen mode Exit fullscreen mode
tar -xzf perl-5.36.1.tar.gz
Enter fullscreen mode Exit fullscreen mode
cd perl-5.36.1
Enter fullscreen mode Exit fullscreen mode

Notes

The website's page mentions:

Read both INSTALL and README.android [sic] in the perl-5.36.1 directory for more detailed information.

Doing so I found that there is actually some useful information about cross-compilation in general, and for Android specifically.

It mentions that there's two methods for cross-compilation, one that requires access to a target system, and a second which doesn't. Since it doesn't explain the second method, we'll setup a target system in the next step.

3. Set up an ARM based emulator

Create a device in the Virtual Device Manager tool included in the Android SDK (or Android Studio).

You can use the recommended image, which will most likely be an arm64 image, if not select an ARM Image manually instead.

After the device is created, ▶️ start the emulator.

This will be used as the target system with the target architecture for cross compilation.

After you finish this whole process, you may want to do it again and compile for different architectures. At this point you'll be creating other devices with the armeabi (32 bit arm) image, or the x86 and x86_64 images.

4. Prepare the environment for cross-compilation

This took some trial and error, so to spare you the pain here's a script that sets up the environment for you:

### Make sure to check/modify this path to match your NDK location
export NDK=$HOME/Library/Android/sdk/ndk/25.2.9519653

### Confirm this by running `adb devices`
export DEVICE=emulator-5554

### A temporary path on the device that will be used during cross-compilation
export TARGETDIR=/data/local/tmp

export TARGET_ARCH=aarch64-linux-android
### OR use one of these other architectures instead:
# export TARGET_ARCH=armv7a-linux-androideabi
# export TARGET_ARCH=x86_64-linux-android
# export TARGET_ARCH=i686-linux-android

export TARGET_LIB_ARCH=$TARGET_ARCH
### OR if using armv7a-linux-androideabi as TARGET_ARCH, use this instead
# export TARGET_LIB_ARCH=arm-linux-androideabi

export HOST_TAG=darwin-x86_64
### OR if using linux, use this instead:
# export HOST_TAG=linux-x86_64

## The API level of the Android version you are targeting
export BUILD_VERSION=23 
## (Could also maybe use 21, but may run into compilation issues)

export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/$HOST_TAG
export SYSROOT=$TOOLCHAIN/sysroot
export LIB_ARCH=$TOOLCHAIN/sysroot/usr/lib/$TARGET_LIB_ARCH/$BUILD_VERSION
export AR=$TOOLCHAIN/bin/llvm-ar
export AS=$TOOLCHAIN/bin/llvm-a
export CC=$TOOLCHAIN/bin/${TARGET_ARCH}${BUILD_VERSION}-clang
export CXX=$TOOLCHAIN/bin/${TARGET_ARCH}${BUILD_VERSION}-clang++
export LD=$TOOLCHAIN/bin/ld
export RANLIB=$TOOLCHAIN/bin/llvm-ranlib
export STRIP=$TOOLCHAIN/bin/llvm-strip

export PATH=$PATH:$TOOLCHAIN/bin
Enter fullscreen mode Exit fullscreen mode

5. Compile Perl with Configure and make

After executing the environment set-up script, execute the configure script:

./Configure -des -Dusecrosscompile -Dtargetarch=$TARGET_ARCH -Dtargetrun=adb -Dcc=$CC -Dcxx=$CXX -Dranlib=$RANLIB -Dsysroot=$SYSROOT -Dlibpth=$LIB_ARCH -Dtargetdir=$TARGETDIR -Dtargethost=$DEVICE
Enter fullscreen mode Exit fullscreen mode

If you get this error: ./Configure: line 4700: ./try: cannot execute binary file Try editing the ./Configure file and modifying the source code to comment out lines slightly before 4700 # Really old versions of gcc for about 53 lines until $rm -f try try.*

(Click here to see the exact lines I commented out for reference.)
# $cat >try.c <<'EOCP'
# int main(int argc, char **argv) {
#     argc = argc + 1;
#     /* This is deliberately a declaration after a statement. */
#     unsigned long long count = 0;
#     for (char **p = argv; *p; ++p) {
#         ++count;
#     }
#     return count == 1 ? 0 : argc;
# }
# EOCP
# c99_for=no
# for flag in '' '-std=gnu99' '-std=c99'; do
#     if $cc -o try $flag $ccflags $ldflags try.c 2>/dev/null && ./try; then
#         c99_for="$flag"
#         break;
#     fi
# done
# case "$c99_for" in
# '') echo "Your C compiler doesn't need any special flags to compile C99 code"
#     ;;
# no) echo >&4 "Your C compiler doesn't seem to be able to compile C99 code"
#     rp='Do you really want to continue?'
#     dflt='n'
#     . ./myread
#     case "$ans" in
#   [yY])   echo >&4 "Okay, continuing."    ;;
#   *)  exit 1              ;;
#     esac
#     ;;
# *)  echo "Your C compiler needs $c99_for to compile C99 code"
#     ccflags="$c99_for $ccflags"
#     ;;
# esac
# $rm -f try try.*

And compile it all by executing:

make
Enter fullscreen mode Exit fullscreen mode

6. Extract the perl binary and lib/ outputs

If everything ran without error, you'll find that a perl binary file is now in your working directory (the source file directory we started in)

Congratulations, we have compiled Perl for Android 🎉

Now let's copy this perl binary file and the lib/ directory and run to test these outputs in the target system.

7. Try things out using ADB

Push the output files into the emulator

adb push perl /data/local/tmp
Enter fullscreen mode Exit fullscreen mode
adb push lib/ /data/local/tmp
Enter fullscreen mode Exit fullscreen mode

Start an ADB shell

adb shell
Enter fullscreen mode Exit fullscreen mode

In the shell try out your Perl distribution 🤩

cd /data/local/tmp
Enter fullscreen mode Exit fullscreen mode
export PERL5LIB=/data/local/tmp/lib
Enter fullscreen mode Exit fullscreen mode
./perl -e "print \"Hello from Android!\";"
Enter fullscreen mode Exit fullscreen mode

It should print something out, if not try to make the file executable with

chmod +x ./perl
Enter fullscreen mode Exit fullscreen mode

That's it! Hopefully this all worked for you.

If you want more ideas on how to use your Perl in an Android app the article I found has some ideas.

Let me know if you run into issues in the comments and I will try to work it out with you 🙌

Top comments (0)