Files
Artemis-Eapil/tests/native_actions_fixture/build.py
T

152 lines
4.6 KiB
Python

"""Build the disposable native fixture using an existing Android SDK/JDK only."""
import argparse
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import zipfile
def build(output: Path) -> Path:
sdk = Path(
os.environ.get("ANDROID_HOME")
or os.environ.get("ANDROID_SDK_ROOT")
or Path.home() / "AppData/Local/Android/Sdk"
)
versions = sorted(
(sdk / "build-tools").glob("*"),
key=lambda p: tuple(int(v) for v in p.name.split(".") if v.isdigit()),
)
platforms = sorted(
(sdk / "platforms").glob("android-*"), key=lambda p: int(p.name.split("-")[-1])
)
if not versions or not platforms:
raise RuntimeError("An existing Android SDK with platform and build-tools is required.")
tools = versions[-1]
android = platforms[-1] / "android.jar"
executable = ".exe" if os.name == "nt" else ""
def run(*args: object) -> None:
subprocess.run([str(arg) for arg in args], check=True, timeout=90)
java = shutil.which("java")
javac = shutil.which("javac")
keytool = shutil.which("keytool")
if not java or not javac or not keytool:
raise RuntimeError("An existing JDK (java, javac and keytool) is required.")
output = output.resolve()
output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="artemis-native-fixture-") as temporary:
scratch = Path(temporary)
classes = scratch / "classes"
classes.mkdir()
manifest = scratch / "AndroidManifest.xml"
manifest.write_text(
"""<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.artemis.fixture">
<application android:label="ARTEMIS Native Fixture" android:debuggable="true" android:allowBackup="false" android:theme="@android:style/Theme.Material.Light.NoActionBar">
<activity android:name=".MainActivity" android:exported="true" android:windowSoftInputMode="adjustResize">
<intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter>
</activity>
</application>
</manifest>""",
encoding="utf-8",
)
unsigned = scratch / "unsigned.apk"
run(
tools / ("aapt2" + executable),
"link",
"-I",
android,
"--manifest",
manifest,
"--min-sdk-version",
26,
"--target-sdk-version",
35,
"--version-code",
1,
"--version-name",
"1.0",
"-o",
unsigned,
)
run(
javac,
"-encoding",
"UTF-8",
"-source",
8,
"-target",
8,
"-Xlint:-options",
"-cp",
android,
"-d",
classes,
Path(__file__).with_name("MainActivity.java"),
)
run(
java,
"-cp",
tools / "lib/d8.jar",
"com.android.tools.r8.D8",
"--lib",
android,
"--min-api",
26,
"--output",
scratch,
*classes.rglob("*.class"),
)
with zipfile.ZipFile(unsigned, "a", compression=zipfile.ZIP_DEFLATED) as archive:
archive.write(scratch / "classes.dex", "classes.dex")
aligned = scratch / "aligned.apk"
run(tools / ("zipalign" + executable), "-f", "-p", 4, unsigned, aligned)
key = scratch / "fixture.keystore"
# Public throwaway debug signing material, never reused for distribution.
run(
keytool,
"-genkeypair",
"-keystore",
key,
"-storepass",
"android",
"-keypass",
"android",
"-alias",
"fixture",
"-keyalg",
"RSA",
"-validity",
365,
"-dname",
"CN=ARTEMIS Test Fixture",
"-noprompt",
)
run(
java,
"-jar",
tools / "lib/apksigner.jar",
"sign",
"--ks",
key,
"--ks-pass",
"pass:android",
"--key-pass",
"pass:android",
"--ks-key-alias",
"fixture",
"--out",
output,
aligned,
)
return output
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, required=True)
print(build(parser.parse_args().output))