diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc51c5f36..a44471c01 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -423,3 +423,59 @@ jobs: -Werror=dev \ -B build_x64 cmake --build build_x64 --config Release --verbose + + android: + needs: [src] + runs-on: ubuntu-latest + outputs: + android-aar: ${{ steps.releaser.outputs.android-aar }} + steps: + - name: 'Set up Python' + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: 'Fetch build-release.py' + uses: actions/checkout@v4 + with: + sparse-checkout: 'build-scripts/build-release.py' + - name: 'Setup Android NDK' + uses: nttld/setup-ndk@v1 + id: setup_ndk + with: + local-cache: true + ndk-version: r21e + - name: 'Setup Java JDK' + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '11' + - name: 'Install ninja' + run: | + sudo apt-get update -y + sudo apt-get install -y ninja-build + - name: 'Download source archives' + uses: actions/download-artifact@v4 + with: + name: sources + path: '${{ github.workspace }}' + - name: 'Untar ${{ needs.src.outputs.src-tar-gz }}' + id: tar + run: | + mkdir -p /tmp/tardir + tar -C /tmp/tardir -v -x -f "${{ github.workspace }}/${{ needs.src.outputs.src-tar-gz }}" + echo "path=/tmp/tardir/${{ needs.src.outputs.project }}-${{ needs.src.outputs.version }}" >>$GITHUB_OUTPUT + - name: 'Build Android prefab binary archive(s)' + id: releaser + run: | + python build-scripts/build-release.py \ + --create android \ + --commit ${{ inputs.commit }} \ + --project SDL3 \ + --root "${{ steps.tar.outputs.path }}" \ + --github \ + --debug + - name: 'Store Android archive(s)' + uses: actions/upload-artifact@v4 + with: + name: android + path: '${{ github.workspace }}/dist' \ No newline at end of file diff --git a/build-scripts/android-prefab.sh b/build-scripts/android-prefab.sh deleted file mode 100755 index 2cbdaa81c..000000000 --- a/build-scripts/android-prefab.sh +++ /dev/null @@ -1,315 +0,0 @@ -#!/bin/bash - -set -e - -if ! [ "x$ANDROID_NDK_HOME" != "x" -a -d "$ANDROID_NDK_HOME" ]; then - echo "ANDROID_NDK_HOME environment variable is not set" - exit 1 -fi - -if ! [ "x$ANDROID_HOME" != "x" -a -d "$ANDROID_HOME" ]; then - echo "ANDROID_HOME environment variable is not set" - exit 1 -fi - -if [ "x$ANDROID_API" = "x" ]; then - ANDROID_API="$(ls "$ANDROID_HOME/platforms" | grep -E "^android-[0-9]+$" | sed 's/android-//' | sort -n -r | head -1)" - if [ "x$ANDROID_API" = "x" ]; then - echo "No Android platform found in $ANDROID_HOME/platforms" - exit 1 - fi -else - if ! [ -d "$ANDROID_HOME/platforms/android-$ANDROID_API" ]; then - echo "Android api version $ANDROID_API is not available ($ANDROID_HOME/platforms/android-$ANDROID_API does not exist)" >2 - exit 1 - fi -fi - -android_platformdir="$ANDROID_HOME/platforms/android-$ANDROID_API" - -echo "Building for android api version $ANDROID_API" -echo "android_platformdir=$android_platformdir" - -scriptdir=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)") -sdl_root=$(cd -P -- "$(dirname -- "$0")/.." && printf '%s\n' "$(pwd -P)") - -build_root="${sdl_root}/build-android-prefab" - -android_abis="armeabi-v7a arm64-v8a x86 x86_64" -android_api=19 -android_ndk=21 -android_stl="c++_shared" - -sdl_major=$(sed -ne 's/^#define SDL_MAJOR_VERSION *//p' "${sdl_root}/include/SDL3/SDL_version.h") -sdl_minor=$(sed -ne 's/^#define SDL_MINOR_VERSION *//p' "${sdl_root}/include/SDL3/SDL_version.h") -sdl_micro=$(sed -ne 's/^#define SDL_MICRO_VERSION *//p' "${sdl_root}/include/SDL3/SDL_version.h") -sdl_version="${sdl_major}.${sdl_minor}.${sdl_micro}" -echo "Building Android prefab package for SDL version $sdl_version" - -prefabhome="${build_root}/prefab-${sdl_version}" -rm -rf "$prefabhome" -mkdir -p "${prefabhome}" - -build_cmake_projects() { - for android_abi in $android_abis; do - echo "Configuring CMake project for $android_abi" - cmake -S "$sdl_root" -B "${build_root}/build_${android_abi}" \ - -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake" \ - -DANDROID_PLATFORM=${android_platform} \ - -DANDROID_ABI=${android_abi} \ - -DSDL_SHARED=ON \ - -DSDL_STATIC=ON \ - -DSDL_STATIC_PIC=ON \ - -DSDL_TEST=ON \ - -DSDL_DISABLE_INSTALL=OFF \ - -DCMAKE_INSTALL_PREFIX="${build_root}/build_${android_abi}/prefix" \ - -DCMAKE_INSTALL_INCLUDEDIR=include \ - -DCMAKE_INSTALL_LIBDIR=lib \ - -DCMAKE_BUILD_TYPE=Release \ - -GNinja - - rm -rf "${build_root}/build_${android_abi}/prefix" - - echo "Building CMake project for $android_abi" - cmake --build "${build_root}/build_${android_abi}" - - echo "Installing CMake project for $android_abi" - cmake --install "${build_root}/build_${android_abi}" - done -} - -classes_sources_jar_path="${prefabhome}/classes-sources.jar" -classes_jar_path="${prefabhome}/classes.jar" -compile_java() { - classes_sources_root="${prefabhome}/classes-sources" - - rm -rf "${classes_sources_root}" - mkdir -p "${classes_sources_root}/META-INF" - - echo "Copying LICENSE.txt to java build folder" - cp "$sdl_root/LICENSE.txt" "${classes_sources_root}/META-INF" - - echo "Copy JAVA sources to java build folder" - cp -r "$sdl_root/android-project/app/src/main/java/org" "${classes_sources_root}" - - java_sourceslist_path="${prefabhome}/java_sources.txt" - pushd "${classes_sources_root}" - echo "Collecting sources for classes-sources.jar" - find "." -name "*.java" >"${java_sourceslist_path}" - find "META-INF" -name "*" >>"${java_sourceslist_path}" - - echo "Creating classes-sources.jar" - jar -cf "${classes_sources_jar_path}" "@${java_sourceslist_path}" - popd - - classes_root="${prefabhome}/classes" - mkdir -p "${classes_root}/META-INF" - cp "$sdl_root/LICENSE.txt" "${classes_root}/META-INF" - java_sourceslist_path="${prefabhome}/java_sources.txt" - - echo "Collecting sources for classes.jar" - find "$sdl_root/android-project/app/src/main/java" -name "*.java" >"${java_sourceslist_path}" - - echo "Compiling classes" - javac -encoding utf-8 -classpath "$android_platformdir/android.jar" -d "${classes_root}" "@${java_sourceslist_path}" - - java_classeslist_path="${prefabhome}/java_classes.txt" - pushd "${classes_root}" - find "." -name "*.class" >"${java_classeslist_path}" - find "META-INF" -name "*" >>"${java_classeslist_path}" - echo "Creating classes.jar" - jar -cf "${classes_jar_path}" "@${java_classeslist_path}" - popd -} - -pom_filename="SDL${sdl_major}-${sdl_version}.pom" -pom_filepath="${prefabhome}/${pom_filename}" -create_pom_xml() { - echo "Creating ${pom_filename}" - cat >"${pom_filepath}" < - 4.0.0 - org.libsdl.android - SDL${sdl_major} - ${sdl_version} - aar - SDL${sdl_major} - The AAR for SDL${sdl_major} - https://libsdl.org/ - - - zlib License - https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt - repo - - - - scm:git:https://github.com/libsdl-org/SDL - https://github.com/libsdl-org/SDL - - -EOF -} - -create_aar_androidmanifest() { - echo "Creating AndroidManifest.xml" - cat >"${aar_root}/AndroidManifest.xml" < - - -EOF -} - -echo "Creating AAR root directory" -aar_root="${prefabhome}/SDL${sdl_major}-${sdl_version}" -mkdir -p "${aar_root}" - -aar_metainfdir_path=${aar_root}/META-INF -mkdir -p "${aar_metainfdir_path}" -cp "${sdl_root}/LICENSE.txt" "${aar_metainfdir_path}" - -prefabworkdir="${aar_root}/prefab" -mkdir -p "${prefabworkdir}" - -cat >"${prefabworkdir}/prefab.json" <"${sdl_moduleworkdir}/module.json" <"${abi_sdllibdir}/abi.json" <"${sdl_moduleworkdir}/module.json" <"${abi_sdllibdir}/abi.json" <"${sdl_moduleworkdir}/module.json" <"${abi_sdllibdir}/abi.json" </dev/null ; - mv "${aar_filename}" "${prefabhome}" -popd - -maven_filename="SDL${sdl_major}-${sdl_version}.zip" - -pushd "${prefabhome}" - zip_filename="SDL${sdl_major}-${sdl_version}.zip" - zip "${maven_filename}" "${aar_filename}" "${pom_filename}" 2>/dev/null; - zip -Tv "${zip_filename}" 2>/dev/null; -popd - -echo "Prefab zip is ready at ${prefabhome}/${aar_filename}" -echo "Maven archive is ready at ${prefabhome}/${zip_filename}" diff --git a/build-scripts/build-release.py b/build-scripts/build-release.py index cc1c61471..b6b965040 100755 --- a/build-scripts/build-release.py +++ b/build-scripts/build-release.py @@ -4,6 +4,7 @@ import argparse import collections import contextlib import datetime +import glob import io import json import logging @@ -26,6 +27,24 @@ logger = logging.getLogger(__name__) VcArchDevel = collections.namedtuple("VcArchDevel", ("dll", "imp", "test")) GIT_HASH_FILENAME = ".git-hash" +ANDROID_AVAILABLE_ABIS = [ + "armeabi-v7a", + "arm64-v8a", + "x86", + "x86_64", +] +ANDROID_MINIMUM_API = 19 +ANDROID_TARGET_API = 29 +ANDROID_MINIMUM_NDK = 21 +ANDROID_LIBRARIES = [ + "dl", + "GLESv1_CM", + "GLESv2", + "log", + "android", + "OpenSLES", +] + def itertools_batched(iterator: typing.Iterable, count: int): iterator = iter(iterator) @@ -373,6 +392,8 @@ class Releaser: self.executer.run([ "cmake", "-S", str(self.root), "-B", str(build_path), "--fresh", + f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', "-DSDL_SHARED=ON", "-DSDL_STATIC=ON", "-DSDL_DISABLE_INSTALL_DOCS=ON", @@ -507,6 +528,178 @@ class Releaser: self._zip_add_git_hash(zip_file=zf, root=archive_prefix) self.artifacts["VC-devel"] = zip_path + def detect_android_api(self, android_home: str) -> typing.Optional[int]: + platform_dirs = list(Path(p) for p in glob.glob(f"{android_home}/platforms/android-*")) + re_platform = re.compile("android-([0-9]+)") + platform_versions = [] + for platform_dir in platform_dirs: + logger.debug("Found Android Platform SDK: %s", platform_dir) + if m:= re_platform.match(platform_dir.name): + platform_versions.append(int(m.group(1))) + platform_versions.sort() + logger.info("Available platform versions: %s", platform_versions) + platform_versions = list(filter(lambda v: v >= ANDROID_MINIMUM_API, platform_versions)) + logger.info("Valid platform versions (>=%d): %s", ANDROID_MINIMUM_API, platform_versions) + if not platform_versions: + return None + android_api = platform_versions[0] + logger.info("Selected API version %d", android_api) + return android_api + + def get_prefab_json_text(self): + return textwrap.dedent(f"""\ + {{ + "schema_version": 2, + "name": "{self.project}", + "version": "{self.version}", + "dependencies": [] + }} + """) + + def get_prefab_module_json_text(self, library_name: str, extra_libs: list[str]): + export_libraries_str = ", ".join(f"\"-l{lib}\"" for lib in extra_libs) + return textwrap.dedent(f"""\ + {{ + "export_libraries": [{export_libraries_str}], + "library_name": "lib{library_name}" + }} + """) + + def get_prefab_abi_json_text(self, abi: str, cpp: bool, shared: bool): + return textwrap.dedent(f"""\ + {{ + "abi": "{abi}", + "api": {ANDROID_MINIMUM_API}, + "ndk": {ANDROID_MINIMUM_NDK}, + "stl": "{'c++_shared' if cpp else 'none'}", + "static": {'true' if not shared else 'false'} + }} + """) + + def get_android_manifest_text(self): + return textwrap.dedent(f"""\ + + + + """) + + def create_android_archives(self, android_api: int, android_home: Path, android_ndk_home: Path, android_abis: list[str]): + cmake_toolchain_file = Path(android_ndk_home) / "build/cmake/android.toolchain.cmake" + if not cmake_toolchain_file.exists(): + logger.error("CMake toolchain file does not exist (%s)", cmake_toolchain_file) + raise SystemExit(1) + aar_path = self.dist_path / f"{self.project}-{self.version}.aar" + added_global_files = False + with zipfile.ZipFile(aar_path, "w", compression=zipfile.ZIP_DEFLATED) as zip_object: + zip_object.writestr("AndroidManifest.xml", self.get_android_manifest_text()) + zip_object.write(self.root / "android-project/app/proguard-rules.pro", arcname="proguard.txt") + zip_object.write(self.root / "LICENSE.txt", arcname="META-INF/LICENSE.txt") + zip_object.writestr("prefab/prefab.json", self.get_prefab_json_text()) + self._zip_add_git_hash(zip_file=zip_object) + + for android_abi in android_abis: + with self.section_printer.group(f"Building for Android {android_api} {android_abi}"): + build_dir = self.root / "build-android" / f"{android_abi}-build" + install_dir = self.root / "install-android" / f"{android_abi}-install" + shutil.rmtree(install_dir, ignore_errors=True) + assert not install_dir.is_dir(), f"{install_dir} should not exist prior to build" + cmake_args = [ + "cmake", + "-S", str(self.root), + "-B", str(build_dir), + "--fresh", + f'''-DCMAKE_C_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + f'''-DCMAKE_CXX_FLAGS="-ffile-prefix-map={self.root}=/src/{self.project}"''', + "-DCMAKE_BUILD_TYPE=RelWithDebInfo", + f"-DCMAKE_TOOLCHAIN_FILE={cmake_toolchain_file}", + f"-DANDROID_PLATFORM={android_api}", + f"-DANDROID_ABI={android_abi}", + f"-DCMAKE_POSITION_INDEPENDENT_CODE=ON", + "-DSDL_SHARED=ON", + "-DSDL_STATIC=ON", + "-DSDL_STATIC_PIC=ON", + "-DSDL_TEST_LIBRARY=ON", + "-DSDL_DISABLE_ANDROID_JAR=OFF", + "-DSDL_TESTS=OFF", + f"-DCMAKE_INSTALL_PREFIX={install_dir}", + "-DSDL_DISABLE_INSTALL=OFF", + "-DSDL_DISABLE_INSTALL_DOCS=OFF", + "-DCMAKE_INSTALL_INCLUDEDIR=include ", + "-DCMAKE_INSTALL_LIBDIR=lib", + "-DCMAKE_INSTALL_DATAROOTDIR=share", + "-DCMAKE_BUILD_TYPE=Release", + f"-G{self.cmake_generator}", + ] + build_args = [ + "cmake", + "--build", str(build_dir), + "--config", "RelWithDebInfo", + ] + install_args = [ + "cmake", + "--install", str(build_dir), + "--config", "RelWithDebInfo", + ] + self.executer.run(cmake_args) + self.executer.run(build_args) + self.executer.run(install_args) + + main_so_library = install_dir / "lib" / f"lib{self.project}.so" + logger.debug("Expecting library %s", main_so_library) + assert main_so_library.is_file(), "CMake should have built a shared library (e.g. libSDL3.so)" + + main_static_library = install_dir / "lib" / f"lib{self.project}.a" + logger.debug("Expecting library %s", main_static_library) + assert main_static_library.is_file(), "CMake should have built a static library (e.g. libSDL3.a)" + + test_library = install_dir / "lib" / f"lib{self.project}_test.a" + logger.debug("Expecting library %s", test_library) + assert test_library.is_file(), "CMake should have built a static test library (e.g. libSDL3_test.a)" + + java_jar = install_dir / f"share/java/{self.project}/{self.project}-{self.version}.jar" + logger.debug("Expecting java archive: %s", java_jar) + assert java_jar.is_file(), "CMake should have compiled the java sources and archived them into a JAR" + + javasources_jar = install_dir / f"share/java/{self.project}/{self.project}-{self.version}-sources.jar" + logger.debug("Expecting java sources archive %s", javasources_jar) + assert javasources_jar.is_file(), "CMake should have archived the java sources into a JAR" + + javadoc_dir = install_dir / "share/javadoc" / self.project + logger.debug("Expecting javadoc archive %s", javadoc_dir) + assert javadoc_dir.is_dir(), "CMake should have built javadoc documentation for the java sources" + if not added_global_files: + zip_object.write(java_jar, arcname="classes.jar") + zip_object.write(javasources_jar, arcname="classes-sources.jar", ) + doc_jar_path = install_dir / "classes-doc.jar" + + javadoc_jar_args = ["jar", "--create", "--file", str(doc_jar_path)] + for fn in javadoc_dir.iterdir(): + javadoc_jar_args.extend(["-C", str(javadoc_dir), fn.name]) + self.executer.run(javadoc_jar_args) + zip_object.write(doc_jar_path, arcname="classes-doc.jar") + + for header in (install_dir / "include" / self.project).iterdir(): + zip_object.write(header, arcname=f"prefab/modules/{self.project}-shared/include/{self.project}/{header.name}") + zip_object.write(header, arcname=f"prefab/modules/{self.project}-static/include/{self.project}/{header.name}") + + zip_object.writestr(f"prefab/modules/{self.project}-shared/module.json", self.get_prefab_module_json_text(library_name=self.project, extra_libs=[])) + zip_object.writestr(f"prefab/modules/{self.project}-static/module.json", self.get_prefab_module_json_text(library_name=self.project, extra_libs=list(ANDROID_LIBRARIES))) + zip_object.writestr(f"prefab/modules/{self.project}_test/module.json", self.get_prefab_module_json_text(library_name=f"{self.project}_test", extra_libs=list())) + added_global_files = True + + zip_object.write(main_so_library, arcname=f"prefab/modules/{self.project}-shared/libs/android.{android_abi}/lib{self.project}.so") + zip_object.writestr(f"prefab/modules/{self.project}-shared/libs/android.{android_abi}/abi.json", self.get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=True)) + zip_object.write(main_static_library, arcname=f"prefab/modules/{self.project}-static/libs/android.{android_abi}/lib{self.project}.a") + zip_object.writestr(f"prefab/modules/{self.project}-static/libs/android.{android_abi}/abi.json", self.get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=False)) + zip_object.write(test_library, arcname=f"prefab/modules/{self.project}_test/libs/android.{android_abi}/lib{self.project}_test.a") + zip_object.writestr(f"prefab/modules/{self.project}_test/libs/android.{android_abi}/abi.json", self.get_prefab_abi_json_text(abi=android_abi, cpp=False, shared=False)) + + self.artifacts[f"android-prefab-aar"] = aar_path + @classmethod def extract_sdl_version(cls, root: Path, project: str): with open(root / f"include/{project}/SDL_version.h", "r") as f: @@ -523,10 +716,14 @@ def main(argv=None): parser.add_argument("--out", "-o", metavar="DIR", dest="dist_path", type=Path, default="dist", help="Output directory") parser.add_argument("--github", action="store_true", help="Script is running on a GitHub runner") parser.add_argument("--commit", default="HEAD", help="Git commit/tag of which a release should be created") - parser.add_argument("--project", required=True, help="Name of the project") - parser.add_argument("--create", choices=["source", "mingw", "win32", "xcframework"], required=True,action="append", dest="actions", help="SDL version") + parser.add_argument("--project", required=True, help="Name of the project (e.g. SDL3") + parser.add_argument("--create", choices=["source", "mingw", "win32", "xcframework", "android"], required=True, action="append", dest="actions", help="What to do") parser.set_defaults(loglevel=logging.INFO) parser.add_argument('--vs-year', dest="vs_year", help="Visual Studio year") + parser.add_argument('--android-api', type=int, dest="android_api", help="Android API version") + parser.add_argument('--android-home', dest="android_home", default=os.environ.get("ANDROID_HOME"), help="Android Home folder") + parser.add_argument('--android-ndk-home', dest="android_ndk_home", default=os.environ.get("ANDROID_NDK_HOME"), help="Android NDK Home folder") + parser.add_argument('--android-abis', dest="android_abis", nargs="*", choices=ANDROID_AVAILABLE_ABIS, default=list(ANDROID_AVAILABLE_ABIS), help="Android NDK Home folder") parser.add_argument('--cmake-generator', dest="cmake_generator", default="Ninja", help="CMake Generator") parser.add_argument('--debug', action='store_const', const=logging.DEBUG, dest="loglevel", help="Print script debug information") parser.add_argument('--dry-run', action='store_true', dest="dry", help="Don't execute anything") @@ -581,14 +778,14 @@ def main(argv=None): raise Exception("The git repo contains modified and/or non-committed files. Run with --force to ignore.") with section_printer.group("Arguments"): - print(f"project = {args.project}") - print(f"version = {releaser.version}") - print(f"commit = {args.commit}") - print(f"out = {args.dist_path}") - print(f"actions = {args.actions}") - print(f"dry = {args.dry}") - print(f"force = {args.force}") - print(f"cmake_generator = {args.cmake_generator}") + print(f"project = {args.project}") + print(f"version = {releaser.version}") + print(f"commit = {args.commit}") + print(f"out = {args.dist_path}") + print(f"actions = {args.actions}") + print(f"dry = {args.dry}") + print(f"force = {args.force}") + print(f"cmake_generator = {args.cmake_generator}") releaser.prepare() @@ -623,6 +820,31 @@ def main(argv=None): if "mingw" in args.actions: releaser.create_mingw_archives() + if "android" in args.actions: + if args.android_home is None or not Path(args.android_home).is_dir(): + parser.error("Invalid $ANDROID_HOME or --android-home: must be a directory containing the Android SDK") + if args.android_ndk_home is None or not Path(args.android_ndk_home).is_dir(): + parser.error("Invalid $ANDROID_NDK_HOME or --android_ndk_home: must be a directory containing the Android NDK") + if args.android_api is None: + with section_printer.group("Detect Android APIS"): + args.android_api = releaser.detect_android_api(android_home=args.android_home) + if args.android_api is None or not (Path(args.android_home) / f"platforms/android-{args.android_api}").is_dir(): + parser.error("Invalid --android-api, and/or could not be detected") + if not args.android_abis: + parser.error("Need at least one Android ABI") + with section_printer.group("Android arguments"): + print(f"android_home = {args.android_home}") + print(f"android_ndk_home = {args.android_ndk_home}") + print(f"android_api = {args.android_api}") + print(f"android_abis = {args.android_abis}") + releaser.create_android_archives( + android_api=args.android_api, + android_home=args.android_home, + android_ndk_home=args.android_ndk_home, + android_abis=args.android_abis, + ) + + with section_printer.group("Summary"): print(f"artifacts = {releaser.artifacts}") diff --git a/docs/README-android.md b/docs/README-android.md index 94260dab1..2f728f5a6 100644 --- a/docs/README-android.md +++ b/docs/README-android.md @@ -377,9 +377,9 @@ Memory debugging The best (and slowest) way to debug memory issues on Android is valgrind. Valgrind has support for Android out of the box, just grab code using: - svn co svn://svn.valgrind.org/valgrind/trunk valgrind + git clone https://sourceware.org/git/valgrind.git -... and follow the instructions in the file README.android to build it. +... and follow the instructions in the file `README.android` to build it. One thing I needed to do on macOS was change the path to the toolchain, and add ranlib to the environment variables: