Skip to content

Instantly share code, notes, and snippets.

Created February 15, 2019 19:14
What would you like to do?
Script to analyze Swift usage in iOS or Mac apps
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 =["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(["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 {}')
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'] =
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.writerows(map(analyze_app, apps))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment