Skip to content

Commit

Permalink
Add configuration for jitpack.
Browse files Browse the repository at this point in the history
Jitpack is a pseudo-repository for Android (and other) artifacts that
builds AARs from GitHub repos for projects that do not themselves
publish their artifacts to somewhere like Maven Central. Adding this
allows Android app developers to add a dependency on
com.github.khronos:vulkan-validationlayers:$TAG to their build.gradle
file rather than needing to manage the VVL libraries themselves. See the
comment at the top of jitpack.yml for more information.

Fixes KhronosGroup#8167
  • Loading branch information
DanAlbert committed Jul 30, 2024
1 parent 31118fe commit 84bbabb
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 18 deletions.
17 changes: 17 additions & 0 deletions jitpack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# This file enables jitpack.io to build this repository into an AAR (Android
# Archive) for easier consumption by app developers. Instead of needing to
# download the VVL release artifacts from GitHub and check those libraries into
# their repository, they can instead depend on
# com.github.khronos:vulkan-validationlayers:$TAG. Jitpack will build the
# repository into an AAR and serve that artifact to developers.
#
# One caveat: if the VVL build is not completely deterministic (unlikely), the
# artifacts served from jitpack will not exactly match those hosted on the
# GitHub Release page, since jitpack will build the artifacts rather than serve
# the ones from the release.
#
# https://jitpack.io/docs/BUILDING/
# https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8167
# https://github.com/KhronosGroup/Vulkan-ValidationLayers/pull/8303
install:
- python scripts/jitpack.py
54 changes: 36 additions & 18 deletions scripts/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@

import argparse
import os
from pathlib import Path
import sys
import shutil
import common_ci


MIN_SDK_VERSION = 26


# Manifest file describing out test application
def get_android_manifest() -> str:
manifest = common_ci.RepoRelative('tests/android/AndroidManifest.xml')
Expand Down Expand Up @@ -77,23 +82,13 @@ def generate_apk(SDK_ROOT : str, CMAKE_INSTALL_DIR : str) -> str:
# https://en.wikipedia.org/wiki/Apk_(file_format)#Package_contents
#
# As a result CMake will need to be run multiple times to create a complete test APK that can be run on any Android device.
def main():
configs = ['Release', 'Debug', 'MinSizeRel']

parser = argparse.ArgumentParser()
parser.add_argument('--config', type=str, choices=configs, default=configs[0])
parser.add_argument('--app-abi', dest='android_abi', type=str, default="arm64-v8a")
parser.add_argument('--app-stl', dest='android_stl', type=str, choices=["c++_static", "c++_shared"], default="c++_static")
parser.add_argument('--apk', action='store_true', help='Generate an APK as a post build step.')
parser.add_argument('--clean', action='store_true', help='Cleans CMake build artifacts')
args = parser.parse_args()

cmake_config = args.config
android_abis = args.android_abi.split(" ")
android_stl = args.android_stl
create_apk = args.apk
clean = args.clean

def build(
cmake_config: str,
android_abis: list[str],
android_stl: str,
create_apk: bool = False,
clean: bool = False,
) -> Path:
if "ANDROID_NDK_HOME" not in os.environ:
print("Cannot find ANDROID_NDK_HOME!")
sys.exit(1)
Expand Down Expand Up @@ -159,7 +154,7 @@ def main():
cmake_cmd += f' -D BUILD_TESTS={create_apk}'
cmake_cmd += f' -D CMAKE_ANDROID_STL_TYPE={android_stl}'

cmake_cmd += ' -D ANDROID_PLATFORM=26'
cmake_cmd += f' -D ANDROID_PLATFORM={MIN_SDK_VERSION}'
cmake_cmd += ' -D ANDROID_USE_LEGACY_TOOLCHAIN_FILE=NO'

common_ci.RunShellCmd(cmake_cmd)
Expand All @@ -175,5 +170,28 @@ def main():
if create_apk:
generate_apk(SDK_ROOT = android_sdk_root, CMAKE_INSTALL_DIR = cmake_install_dir)

return Path(cmake_install_dir)


def main() -> None:

configs = ['Release', 'Debug', 'MinSizeRel']

parser = argparse.ArgumentParser()
parser.add_argument('--config', type=str, choices=configs, default=configs[0])
parser.add_argument('--app-abi', dest='android_abi', type=str, default="arm64-v8a")
parser.add_argument('--app-stl', dest='android_stl', type=str, choices=["c++_static", "c++_shared"], default="c++_static")
parser.add_argument('--apk', action='store_true', help='Generate an APK as a post build step.')
parser.add_argument('--clean', action='store_true', help='Cleans CMake build artifacts')
args = parser.parse_args()

build(
args.config,
args.android_abi.split(" "),
args.android_sdk,
args.apk,
args.clean,
)

if __name__ == '__main__':
main()
142 changes: 142 additions & 0 deletions scripts/jitpack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
#
# Copyright (c) 2024 LunarG, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Entry point for jitpack.yml.
This implements the custom build command used by the jitpack.yml in the top level of
this repo. See the documentation in that file for more information.
"""
import json
import os
import subprocess
import sys
import textwrap
from collections.abc import Iterator
from pathlib import Path
from zipfile import ZipFile

import android
import common_ci


def artifact_info_from_env() -> tuple[str, str, str]:
"""Returns a tuple of [group ID, artifact ID, version]."""
# Jitpack puts this information in the environment:
# https://jitpack.io/docs/BUILDING/#build-environment
try:
return os.environ["GROUP"], os.environ["ARTIFACT"], os.environ["VERSION"]
except KeyError as ex:
sys.exit(f"{ex.args[0]} must be set in the environment")


def ndk_default_abis() -> Iterator[str]:
"""Yields each default NDK ABI.
The NDK includes a meta/abis.json file that enumerates each ABI the NDK is capable
of building for, and includes some descriptive data for each ABI. One of those
fields is "default", which will be true if the ABI is one that the NDK maintainers
recommend including support for by default. Most of the time all ABIs are
recommended to be built by default, but in the rare case where an ABI is under
development, or an old one is deprecated, we can key off that field to avoid them.
At the time of writing (July 2024), the only non-default ABI is riscv64. The NDK r27
changelog says that it's only there for bringup and shouldn't be used for release
artifacts yet.
"""
try:
ndk = Path(os.environ["ANDROID_NDK_HOME"])
except KeyError:
sys.exit("ANDROID_NDK_HOME must be set in the environment")

abis_meta_path = ndk / "meta/abis.json"
if not abis_meta_path.exists():
sys.exit(
f"{abis_meta_path} does not exist. Does ANDROID_NDK_HOME ({ndk}) point to "
"a valid NDK?"
)

with abis_meta_path.open("r", encoding="utf-8") as abis_meta_file:
abis_meta = json.load(abis_meta_file)

for name, data in abis_meta.items():
if data["default"]:
yield name


def generate_aar(
output_path: Path,
library_dir: Path,
group_id: str,
artifact_id: str,
min_sdk_version: int,
) -> None:
"""Creates an AAR from the CMake binaries."""
with ZipFile(output_path, mode="w") as zip_file:
zip_file.writestr(
"AndroidManifest.xml",
textwrap.dedent(
f"""\
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="{group_id}.{artifact_id}" >
<uses-sdk android:minSdkVersion="{min_sdk_version}" />
</manifest>
"""
),
)

for abi_dir in library_dir.iterdir():
libs = list(abi_dir.glob("*.so"))
if not libs:
raise RuntimeError(f"No libraries found matching {abi_dir}/*.so")
for lib in libs:
zip_file.write(lib, arcname=f"jni/{abi_dir.name}/{lib.name}")


def install(aar: Path, group_id: str, artifact_id: str, version: str) -> None:
"""Installs the AAR to the local maven repository."""
# A custom jitpack build must install the artifact to the local maven repository.
# https://jitpack.io/docs/BUILDING/#custom-commands
#
# The jitpack host has mvn installed, so the easiest way to do this is to follow
# https://maven.apache.org/guides/mini/guide-3rd-party-jars-local.html
subprocess.run(
[
"mvn",
"install:install-file",
f"-Dfile={aar}",
f"-DgroupId={group_id}",
f"-DartifactId={artifact_id}",
f"-Dversion={version}",
"-Dpackaging=aar",
],
check=True,
)


def main() -> None:
"""Entry point called by jitpack.yml."""
group_id, artifact_id, version = artifact_info_from_env()
build_dir = android.build("Release", list(ndk_default_abis()), "c++_static")

aar_dir = Path(common_ci.RepoRelative("build-android/aar"))
aar_dir.mkdir(parents=True, exist_ok=True)
aar = aar_dir / f"{artifact_id}-{version}.aar"
generate_aar(aar, build_dir / "lib", group_id, artifact_id, android.MIN_SDK_VERSION)
install(aar, group_id, artifact_id, version)


if __name__ == "__main__":
main()

0 comments on commit 84bbabb

Please sign in to comment.