Skip to content

Instantly share code, notes, and snippets.

@antoinevg
Last active August 15, 2016 12:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save antoinevg/6f9fe7f332b88d5c019e to your computer and use it in GitHub Desktop.
Save antoinevg/6f9fe7f332b88d5c019e to your computer and use it in GitHub Desktop.
Import AAR libraries to a Trigger.io Native Module project
#!/usr/bin/env python
import contextlib
import errno
import json
import os
import shutil
import sys
import tempfile
import zipfile
from os import path
from xml.etree import ElementTree
# Any namespaces must be registered or the parser will rename them
ElementTree.register_namespace('android', 'http://schemas.android.com/apk/res/android')
ElementTree.register_namespace('tools', 'http://schemas.android.com/tools')
android = "{http://schemas.android.com/apk/res/android}"
def main():
# get arguments
argv = sys.argv[1:]
if len(argv) != 2:
print("Usage: import_aar.py <aarfile> <moduledirectory>\n")
sys.exit(1)
aar = argv[0]
out = argv[1]
# sanity check arguments
aar_name = path.splitext(path.basename(aar))
if aar_name[1] != ".aar":
print ("Invalid aar file extension: %s" % aar_name[1])
sys.exit(1)
elif not path.isfile(aar):
print ("Invalid aar file: %s" % aar)
sys.exit(1)
elif not path.isdir(out):
print ("Invalid output directory: %s" % out)
sys.exit(1)
elif not path.isdir(path.join(out, "module", "android")):
print ("Output directory does not contain a valid Android Native Module for forge: %s" % out)
sys.exit(1)
with make_temp_directory() as tmp:
# Step 1: Unzip aar file
print "\nStep 1: Unzipping aar library: '%s'" % aar_name[0]
zip = zipfile.ZipFile(aar)
zip.extractall(path=tmp)
# Step 2: Rename classes.jar
classes_jar = "%s-classes.jar" % aar_name[0]
print "\nStep 2: Renaming classes.jar to '%s'" % classes_jar
if path.isfile(path.join(tmp, "classes.jar")):
shutil.copy(path.join(tmp, "classes.jar"), path.join(tmp, classes_jar))
else:
print "\tNo classes.jar found"
# Step 3: Add jar files to native module
print "\nStep 3: Adding jar files to module directory"
libs_dir = path.join(tmp, "libs")
libdir = path.join(out, "module", "android", "libs")
if not path.exists(libdir):
os.makedirs(libdir)
if path.isfile(path.join(tmp, classes_jar)) and not path.exists(path.join(libdir, classes_jar)):
shutil.move(path.join(tmp, classes_jar), libdir)
print "\tadded '%s'" % classes_jar
if path.isdir(libs_dir):
for file in os.listdir(libs_dir):
if not path.exists(path.join(libdir, file)):
print "\tadded '%s'" % file
shutil.copy(path.join(tmp, "libs", file), libdir)
# Step 4: Pull out libs from jni/
# TODO handle other ABI's as well
print "\nStep 4: Adding JNI libraries to module directory"
if path.isdir(path.join(tmp, "jni", "armeabi-v7a")):
jnidir = path.join(out, "module", "android", "libs", "armeabi-v7a")
if not path.exists(jnidir):
os.makedirs(jnidir)
for file in os.listdir(path.join(tmp, "jni", "armeabi-v7a")):
if not path.exists(path.join(jnidir, file)):
print "\tadded '%s'" % file
shutil.copy(path.join(tmp, "jni", "armeabi-v7a", file), jnidir)
else:
print "\tNo JNI libraries found"
# Step 5: Merge resources
print "\nStep 5: Merging resources"
resdir = path.join(out, "module", "android", "res")
if not path.exists(resdir):
os.makedirs(resdir)
for src_dir, dirs, files in os.walk(path.join(tmp, "res")):
dst_dir = src_dir.replace(path.join(tmp, "res"), resdir)
if not path.exists(dst_dir):
os.mkdir(dst_dir)
for file in files:
src = path.join(src_dir, file)
dst = path.join(dst_dir, file)
if path.exists(dst) and dst.endswith('.xml'):
print("\tmerged resource '%s'" % dst)
merged = _merge_android_resources((src, dst))
with open(dst, 'w') as f:
f.write(merged)
elif path.exists(dst):
print("\tskipped resource '%s' as it already exists" % dst)
else:
print "\tadded resource '%s'" % dst
shutil.copy(src, dst)
# Step 6: Generate build_steps rules from AndroidManifest.xml
print "\nStep 6: Converting AndroidManifest.xml rules to build steps"
steps = []
with open(path.join(tmp, "AndroidManifest.xml")) as f:
root = ElementTree.parse(f).getroot()
for r in root.findall("permission"):
steps.append(add_permission(r))
for r in root.findall("uses-permission"):
steps.append(add_uses_permission(r))
for r in root.find("application").findall("service"):
steps.append(add_service(r))
for r in root.find("application").findall("receiver"):
steps.append(add_receiver(r))
for r in root.find("application").findall("activity"):
steps.append(add_activity(r))
# Step 7: Dump rules from proguard.txt
print "\nStep 7: Extract proguard rules"
proguard = path.join(tmp, "proguard.txt")
if path.isfile(proguard):
lines = [line.rstrip('\n').strip() for line in open(proguard)]
rule = ""
for line in lines:
if line.startswith("#"):
pass
elif line.startswith("-"):
if len(rule) > 0:
steps.append(add_proguard(rule))
rule = line
else:
rule += " " + line
if len(rule) > 0:
steps.append(add_proguard(rule))
# Step 8: Merge with existing build_steps.json
print "\nStep 8: Merge build steps into module/android/build_steps.json"
build_steps = path.join(out, "module", "android", "build_steps.json")
if not path.isfile(build_steps):
print "\tcreating build_steps.json"
with open(build_steps, 'w') as f:
json.dump([], f)
with open(build_steps) as f:
try:
dst = json.load(f)
except:
dst = []
with open(build_steps, 'w') as f:
merged = merge_build_steps(steps, dst)
json.dump(merged, f, indent=4, sort_keys=True)
print "\nFinished: You can now update the Android Inspector from the Toolkit window"
def merge_build_steps(src, dst):
hashes = [make_hash(step) for step in dst]
for step in src:
hash = make_hash(step)
if not hash in hashes:
print "\tmerged step: %s" % ellipsis(str(step))
dst.append(step)
return dst
def ellipsis(s):
return "%s ... %s" % (s[:15], s[-25:])
import copy
def make_hash(o):
"""
Makes a hash from a dictionary, list, tuple or set to any level, that contains
only other hashable types (including any lists, tuples, sets, and
dictionaries).
"""
if isinstance(o, (set, tuple, list)):
return tuple([make_hash(e) for e in o])
elif not isinstance(o, dict):
return hash(o)
new_o = copy.deepcopy(o)
for k, v in new_o.items():
new_o[k] = make_hash(v)
return hash(tuple(frozenset(sorted(new_o.items()))))
def element_to_dict(r):
element = {
"tag": r.tag
}
if len(r.attrib):
element["attributes"] = {}
for a in r.attrib:
name = a.replace(android, "android:")
element["attributes"][name] = r.attrib.get(a)
if len(r.findall("*")):
element["children"] = []
for child in r.findall("*"):
element["children"].append(element_to_dict(child))
return element
def add_permission(r):
step = { "do": { "android_add_to_manifest": { "element": element_to_dict(r) } } }
print "\tadded permission: %s" % ellipsis(r.attrib.get(android + "name"))
return step
def add_uses_permission(r):
permission = r.attrib.get(android + "name")
step = { "do": { "android_add_permission": { "permission": permission } } }
print "\tadded uses-permission: %s" % ellipsis(permission)
return step
def add_service(r):
step = { "do": { "android_add_to_application_manifest": { "element": element_to_dict(r) } } }
print "\tadded service: %s" % ellipsis(r.attrib.get(android + "name"))
return step
def add_activity(r):
step = { "do": { "android_add_to_application_manifest": { "element": element_to_dict(r) } } }
print "\tadded activity: %s" % ellipsis(r.attrib.get(android + "name"))
return step
def add_receiver(r):
step = { "do": { "android_add_to_application_manifest": { "element": element_to_dict(r) } } }
print "\tadded receiver: %s" % ellipsis(r.attrib.get(android + "name"))
return step
def add_proguard(rule):
step = { "do": { "android_add_proguard_rule": { "rule": rule.strip() } } }
print "\tadded proguard rule: %s" % ellipsis(rule)
return step
@contextlib.contextmanager
def make_temp_directory():
temp_dir = tempfile.mkdtemp()
yield temp_dir
print "Cleaning up: %s" % temp_dir
shutil.rmtree(temp_dir)
def _merge_android_resources(filenames):
assert len(filenames) > 0, 'No filenames!'
roots = [ElementTree.parse(f).getroot() for f in filenames]
for r in roots[1:]:
roots[0].extend(r)
return '<?xml version="1.0" encoding="utf-8"?>\n' + ElementTree.tostring(roots[0])
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment