Compiling Perl for Android
Tags: android, perl, compiling, mobile
Published: 2023-06-27 14:05:06

## The Why

I'm building a photo review app for Android, and I want to parse the full EXIF metadata in [Canon RAW CR3](https://github.com/lclevy/canon_cr3) files.

I found no better tool than [ExifTool by Phil Harvey](https://exiftool.org/). 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](https://appmattus.medium.com/embedding-perl-on-android-b271a1f4b013) — 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](https://developer.android.com/ndk/guides/standalone_toolchain). 

🏁 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](https://www.cpan.org/src/)

```sh
curl -O https://www.cpan.org/src/5.0/perl-5.36.1.tar.gz
```
```sh
tar -xzf perl-5.36.1.tar.gz
```
```sh
cd perl-5.36.1
```

#### 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](https://github.com/Perl/perl5/blob/blead/INSTALL#cross-compilation), and [for Android specifically](https://github.com/Perl/perl5/blob/blead/README.android#cross-compilation).

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:

```sh
### 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
```

### 5. Compile Perl with `Configure` and `make`

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

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

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.*` {% details (Click here to see the exact lines I commented out for reference.) %}
```sh
# $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.*
```
{% enddetails %}


And compile it all by executing:

```sh
make
```

### 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
```sh
adb push perl /data/local/tmp
```
```sh
adb push lib/ /data/local/tmp
```

Start an ADB shell
```sh
adb shell
```

In the shell try out your Perl distribution 🤩

```sh
cd /data/local/tmp
```
```sh
export PERL5LIB=/data/local/tmp/lib
```
```sh
./perl -e "print \"Hello from Android!\";"
```

It should print something out, if not try to make the file executable with
```sh
chmod +x ./perl
```

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](https://appmattus.medium.com/embedding-perl-on-android-b271a1f4b013#4d45).

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