-
Notifications
You must be signed in to change notification settings - Fork 4
/
analyze_apps.py
67 lines (56 loc) · 2.64 KB
/
analyze_apps.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/env python3
from pathlib import Path
import plistlib
import subprocess
import re
import csv
def app_uses_swift(path):
libswiftPath = path / 'Frameworks' / 'libswiftCore.dylib'
return libswiftPath.exists()
def is_executable(file_path):
file_info = subprocess.run(["file", file_path], capture_output=True)
return 'Mach-O' in str(file_info.stdout)
def find_executable_in(path):
executables = [file for file in path.iterdir()
if is_executable(file)]
return None if len(executables) == 0 else executables[0]
def class_dump(executable_path):
header = str(subprocess.run(["class-dump", executable_path], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout)
return re.findall("@interface ([\w_]+)\s?:\s?(\w+)", header)
def is_apple_class(class_tuple):
prefixes = ["NS", "UI", "CA", "SCN", "SK", "CI", "AB", "ML", "GK", "AV"]
for prefix in prefixes:
pattern = re.compile(f'{prefix}[A-Z][a-z]+')
if pattern.match(class_tuple[0]):
return True
return False
def percentage_classes_in_swift(classes):
classes = [item for item in classes if not is_apple_class(item)]
if len(classes) == 0: return 0.0
swift_classes = [item for item in classes if item[0].startswith("_T")]
return float(len(swift_classes)) / float(len(classes))
def analyze_app(path):
results = {}
infoPlistPath = path / 'Info.plist'
with open(str(infoPlistPath.resolve()), 'rb') as infoPlistFile:
infoPlist = plistlib.load(infoPlistFile)
bundle_id = infoPlist['CFBundleIdentifier']
app_name = infoPlist.get('CFBundleDisplayName', infoPlist.get('CFBundleName', infoPlist['CFBundleIdentifier']))
print(f'analyzing {app_name} at {path.name}')
results['app_name'] = app_name
results['bundle_id'] = bundle_id
results['sdk'] = infoPlist.get('DTSDKName')
results['deployment_target'] = infoPlist.get('MinimumOSVersion')
results['uses_swift'] = app_uses_swift(path)
executable = find_executable_in(path)
results['executable'] = executable.name
classes = class_dump(executable)
results['percentage_swift'] = percentage_classes_in_swift(classes)
results['main_binary_uses_swift'] = results['percentage_swift'] > 0
return results
apps = [path for path in Path.cwd().iterdir() if path.suffix == '.app']
with open('results.csv', mode='w', newline='') as csv_file:
fieldnames = ['app_name', 'bundle_id', 'sdk', 'deployment_target', 'uses_swift', 'percentage_swift', 'main_binary_uses_swift', 'executable']
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(map(analyze_app, apps))