Troubleshooting Docker Android Builds on ARM Macs:

Posted on

The Initial Setup

I started by using Claude, an AI assistant, to generate the necessary Docker and build script files for my Android project. Claude provided a comprehensive setup that included:

  1. A Dockerfile to create the build environment
  2. A shell script to run the build process inside the Docker container
  3. A script to set up and run everything

The initial scripts looked promising, and I was excited to get started.

The First Hurdle

After setting up the files and running the build script, I encountered my first error:

Error: Could not determine SDK root.
Error: Either specify it explicitly with --sdk_root= or move this package into its expected location: <sdk>/cmdline-tools/latest/

This error suggested that the Android SDK wasn’t set up correctly in the Docker image. Claude helped me adjust the Dockerfile to properly set up the SDK directory structure.

Digging Deeper

After resolving the SDK root issue, I hit another roadblock:

Warning: Dependant package with key emulator not found!
Warning: Unable to compute a complete list of dependencies.

This error occurred when trying to install the necessary SDK components. We tried various approaches, including installing components one by one and adding the emulator package explicitly.

The Breakthrough

Despite these adjustments, the build process was still failing. It was at this point that I had a realization – I was running this on an ARM-based Mac (with an M1 chip). This turned out to be the key to solving the puzzle.

The issue wasn’t with the scripts or the Docker setup per se, but with the architecture mismatch. The Android SDK and many of its tools are primarily designed for x86 architecture, which can cause issues when running on ARM-based systems like the newer Macs.

The Solution

Armed with this knowledge, I asked Claude to modify the setup to account for ARM-based Macs. The solution involved two key changes:

  1. Using the --platform=linux/amd64 flag in the Dockerfile to ensure we’re using the x86_64 version of the image.
  2. Adding the same flag when building and running the Docker container.

Here’s a snippet of the updated Dockerfile:

FROM --platform=linux/amd64 openjdk:11-jdk

# Rest of the Dockerfile remains the same

And the updated run script:

#!/bin/bash

# Build the Docker image
docker build --platform linux/amd64 -t android-build .

# Run the Docker container
docker run --rm --platform linux/amd64 -v $(pwd):/app android-build

These changes tell Docker to use the x86_64 version of the image, which is then emulated on ARM machines using Docker’s built-in emulation capabilities.

Lessons Learned

This experience taught me several valuable lessons:

  1. Always consider the underlying architecture: When setting up development environments, especially in containerized settings, it’s crucial to consider the architecture of both the host machine and the target environment.
  2. Docker’s emulation capabilities are powerful: Docker’s ability to run x86_64 images on ARM architecture is impressive and can be a lifesaver in situations like this.
  3. Persistence pays off: Debugging can be frustrating, but persistence and methodical investigation usually lead to a solution.
  4. AI assistants can be valuable partners: While Claude couldn’t immediately identify the ARM-specific issue, it was instrumental in quickly generating and modifying scripts, allowing me to focus on problem-solving.
  5. Let Claude generate a bash script that creates all files: As Claude can’t yet generate multiple files to download / create in one go, it’s a good trick to let Claude generate a bash script that when run generates all the needed files. (The generated script is at the end of this post)
  6. Use Claude to quickly generate container scripts so you can start legacy projects much faster. Claude picks up on things like Node version, Android SDK Version by providing the build file or other configuration files.

Conclusion

Setting up a Docker environment for Android builds on an ARM-based Mac presented some unique challenges, but the process of discovering and resolving these issues was incredibly educational. By understanding the architectural differences and leveraging Docker’s platform-specific options, we were able to create a solution that works across different architectures.

Remember, if you’re facing similar issues, consider the architecture of your machine and don’t hesitate to use platform-specific Docker options. Happy building!

The whole script which generates all the files needed to build and run the container:

#!/bin/bash

# Create Dockerfile
cat << EOF > Dockerfile
FROM --platform=linux/amd64 openjdk:11-jdk

# Install necessary tools
RUN apt-get update && apt-get install -y wget unzip

# Set up environment variables
ENV ANDROID_HOME /usr/local/android-sdk
ENV PATH \${PATH}:\${ANDROID_HOME}/cmdline-tools/latest/bin:\${ANDROID_HOME}/platform-tools

# Download and install Android SDK
RUN mkdir -p \${ANDROID_HOME} && cd \${ANDROID_HOME} && \
    wget -q https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip && \
    unzip commandlinetools-linux-8512546_latest.zip && \
    rm commandlinetools-linux-8512546_latest.zip && \
    mkdir -p cmdline-tools/latest && \
    mv cmdline-tools/bin cmdline-tools/latest/ && \
    mv cmdline-tools/lib cmdline-tools/latest/ && \
    mv cmdline-tools/NOTICE.txt cmdline-tools/latest/ && \
    mv cmdline-tools/source.properties cmdline-tools/latest/ && \
    rm -rf cmdline-tools/README.txt

# Accept licenses
RUN yes | \${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --sdk_root=\${ANDROID_HOME} --licenses

# Install necessary SDK components
RUN \${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --sdk_root=\${ANDROID_HOME} "platform-tools" "platforms;android-30" "build-tools;30.0.3"

# Set the working directory
WORKDIR /app

# Copy your project files
COPY . .

# Make gradlew executable
RUN chmod +x ./gradlew

# Copy the build script
COPY docker-build-debug.sh /app/docker-build-debug.sh
RUN chmod +x /app/docker-build-debug.sh

# Set the entry point
ENTRYPOINT ["/app/docker-build-debug.sh"]
EOF

# Create docker-build-debug.sh
cat << EOF > docker-build-debug.sh
#!/bin/bash

# Navigate to the project directory
cd /app

# Clean the project
./gradlew clean

# Build debug APK for all flavors
./gradlew assembleDebug

echo "Debug build completed. APKs can be found in the 'app/build/outputs/apk/debug' directory."
EOF

# Create run-docker-build.sh
cat << EOF > run-docker-build.sh
#!/bin/bash

# Build the Docker image
docker build --platform linux/amd64 -t android-build .

# Run the Docker container
docker run --rm --platform linux/amd64 -v \$(pwd):/app android-build
EOF

# Make scripts executable
chmod +x docker-build-debug.sh run-docker-build.sh

echo "Setup complete. Created Dockerfile, docker-build-debug.sh, and run-docker-build.sh"
echo "To build your project, run: ./run-docker-build.sh"

Written by BFF Claude & Me

Leave a Reply