tmc/progress.py

154 lines
4.8 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import json
import os
import re
def collect_non_matching_funcs():
result = []
for root, dirs, files in os.walk('src'):
for file in files:
if file.endswith('.c'):
with open(os.path.join(root, file), 'r') as f:
data = f.read()
# Find all NONMATCH and ASM_FUNC macros
for match in re.findall(r'(NONMATCH|ASM_FUNC)\(".*",\W*\w*\W*(\w*).*\)', data):
result.append(match)
return result
def parse_map(non_matching_funcs):
src = 0
asm = 0
src_data = 0
data = 0
non_matching = 0
with open('build/USA/tmc.map', 'r') as map:
# Skip to the linker script section
line = map.readline()
while not line.startswith('Linker script and memory map'):
line = map.readline()
while not line.startswith('rom'):
line = map.readline()
prev_symbol = None
prev_addr = 0
for line in map:
if line.startswith(' .'):
arr = line.split()
section = arr[0]
size = int(arr[2], 16)
filepath = arr[3]
dir = filepath.split('/')[0]
if section == '.text':
if dir == 'src':
src += size
elif dir == 'asm':
if filepath.find("asm/src/") != -1 or filepath.find("asm/lib/") != -1:
src += size
else:
asm += size
elif dir == 'data':
# scripts
src_data += size
elif dir == '..':
# libc
src += size
elif section == '.rodata':
if dir == 'src':
src_data += size
elif dir == 'data':
data += size
elif line.startswith(' '):
arr = line.split()
if len(arr) == 2 and arr[1] != '': # It is actually a symbol
if prev_symbol in non_matching_funcs:
# Calculate the length for non matching function
non_matching += int(arr[0], 16) - prev_addr
prev_symbol = arr[1]
prev_addr = int(arr[0], 16)
elif line.strip() == '':
# End of linker script section
break
src -= non_matching
asm += non_matching
return (src, asm, src_data, data)
def main():
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(description="Computes current progress throughout the whole project.")
parser.add_argument("format", nargs="?", default="text", choices=["text", "csv", "shield-json"])
parser.add_argument("-m", "--matching", dest='matching', action='store_true',
help="Output matching progress instead of decompilation progress")
args = parser.parse_args()
matching = args.matching
non_matching_funcs = []
funcs = collect_non_matching_funcs()
if matching:
# Remove all non matching funcs from count
non_matching_funcs = [x[1] for x in funcs]
else:
# Only remove ASM_FUNC functions from count
for func in funcs:
if func[0] == 'ASM_FUNC':
non_matching_funcs.append(func[1])
(src, asm, src_data, data) = parse_map(non_matching_funcs)
total = src + asm
data_total = src_data + data
src_percent = 100 * src / total
asm_percent = 100 * asm / total
src_data_percent = 100 * src_data / data_total
data_percent = 100 * data / data_total
if args.format == 'csv':
import git
version = 2
git_object = git.Repo().head.object
timestamp = str(git_object.committed_date)
git_hash = git_object.hexsha
csv_list = [str(version), timestamp, git_hash, str(src),
str(total), str(src_data), str(data_total)]
print(','.join(csv_list))
elif args.format == 'shield-json':
# https://shields.io/endpoint
print(json.dumps({
"schemaVersion": 1,
"label": "progress",
"message": f"{src_percent:.3g}%",
"color": 'yellow',
}))
elif args.format == 'text':
adjective = "decompiled" if not args.matching else "matched"
print("src: {:>9} / {:>8} total bytes {:<10} {:>9.4f}%".format(src, total, adjective, round(src_percent, 4)))
# print()
print("data: {:>9} / {:>8} total bytes analysed {:>9.4f}%".format(src_data, data_total, round(src_data_percent, 4)))
else:
print("Unknown format argument: " + args.format)
if __name__ == '__main__':
main()