152 lines
4.6 KiB
Python
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))
|