Last active
August 15, 2016 12:07
-
-
Save antoinevg/6f9fe7f332b88d5c019e to your computer and use it in GitHub Desktop.
Import AAR libraries to a Trigger.io Native Module project
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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