You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
304 lines
9.5 KiB
304 lines
9.5 KiB
#!/usr/bin/python |
|
|
|
import os |
|
import sys |
|
import re |
|
import subprocess |
|
import argparse |
|
|
|
c_exclude_list = [ |
|
"elm_app", # These are only useful for C apps |
|
"elm_widget", # Custom widgets, probably not feasible for us to provide |
|
"elm_quicklaunch", # Is quicklaunch relevant for us? |
|
"emotion_object_extension_may_play_fast_get", # this optimization does |
|
# not work from py |
|
"edje_edit_", # Disabled |
|
"ecore_thread_", # python has his own thread abstraction library |
|
"ecore_pipe_", # python has his own pipe abstraction library |
|
"ecore_getopt_", # python has his own getopt implementation |
|
"ecore_coroutine_", # python has someting similar...maybe |
|
"ecore_fork_", # low level stuff, not to be exposed |
|
"ecore_timer_dump", # this is just usefull for debugging |
|
"ecore_throttle_", # I don't know what this is :/ - davemds |
|
"elm_check_state_pointer_set", # Cannot be implemented in Python |
|
"elm_access", # Access disabled until 1.9 |
|
"elm_config_access", # Access disabled until 1.9 |
|
"elm_object_item_access", # Access disabled until 1.9 |
|
"elm_systray", # We currently have this in using Eo API |
|
"elm_code_", # The code widget is still in beta |
|
"elm_need_e_dbus", # has been deprecated long time ago |
|
"elm_check_selected_get", # We already provide state_get |
|
"elm_check_selected_set", # We already provide state_set |
|
|
|
] |
|
c_excludes = "|".join(c_exclude_list) |
|
|
|
py_exclude_list = [ |
|
"elm_naviframe_item_simple_push", # macro |
|
"elm_object_item_content", # macro |
|
"elm_object_item_text", # macro |
|
"elm_object_content", # macro |
|
"elm_object_text", # macro |
|
"elm_layout_end", # macros |
|
"elm_layout_icon", # macros |
|
"elm_object_domain_translatable_text", # macros |
|
"elm_object_tooltip_translatable_text", # macros |
|
"elm_object_translatable_text", # macros |
|
"elm_access", # Access disabled until 1.9 |
|
"elm_config_access", # Access disabled until 1.9 |
|
"elm_object_item_access", # Access disabled until 1.9 |
|
] |
|
py_excludes = "|".join(py_exclude_list) |
|
|
|
params = { |
|
"eo": ("include", "Eo", "eo"), |
|
"evas": ("include", "Evas", "evas"), |
|
"ecore": ("efl/ecore", "Ecore", "ecore"), |
|
"ecore-file": ("efl/ecore", "Ecore_File", "ecore_file"), |
|
"ecore-x": ("efl/ecore", "Ecore_X", "ecore_x"), |
|
"edje": ("include", "Edje", "edje"), |
|
"emotion": ("include", "Emotion", "emotion"), |
|
"elementary": ("efl/elementary", "Elementary", "elm"), |
|
} |
|
|
|
parser = argparse.ArgumentParser( |
|
description="Reports EFL vs. Python-EFL API functions coverage" |
|
) |
|
api_group = parser.add_argument_group("api") |
|
api_group.add_argument( |
|
"--python", |
|
action="store_true", default=False, |
|
help="Show Python API coverage" |
|
) |
|
api_group.add_argument( |
|
"--c", |
|
action="store_true", default=False, |
|
help="Show C API coverage" |
|
) |
|
parser.add_argument( |
|
"libs", |
|
nargs="*", |
|
choices=(list(params.keys()) + ["all"]), |
|
default="all" |
|
) |
|
args = parser.parse_args() |
|
|
|
if "all" in args.libs: |
|
args.libs = params.keys() |
|
|
|
if not args.python and not args.c: |
|
args.python = True |
|
args.c = True |
|
|
|
|
|
def pkg_config(require, min_vers=None): |
|
name = require.capitalize() |
|
try: |
|
ver = subprocess.check_output( |
|
["pkg-config", "--modversion", require] |
|
).decode("utf-8").strip() |
|
if min_vers is not None: |
|
assert 0 == subprocess.call( |
|
["pkg-config", "--atleast-version", min_vers, require] |
|
) |
|
cflags = subprocess.check_output( |
|
["pkg-config", "--cflags-only-I", require] |
|
).decode("utf-8").split() |
|
return cflags |
|
except (OSError, subprocess.CalledProcessError): |
|
raise SystemExit( |
|
"Failed to find %s with 'pkg-config'. Please make sure that it " |
|
"is installed and available on your system path." |
|
) % (name) |
|
except (AssertionError): |
|
raise SystemExit( |
|
"Failed to match version. Found: %s Needed: %s" % (ver, min_vers) |
|
) |
|
|
|
|
|
def get_capis(inc_path, prefix): |
|
capis = [] |
|
capilns = [] |
|
capi_pattern = re.compile( |
|
"^ *EAPI [A-Za-z_ *\n]+ *\**\n?(?!" + |
|
c_excludes + ")(" + prefix + |
|
"_\w+) *\(", |
|
flags=re.S | re.M |
|
) |
|
|
|
for path, dirs, files in os.walk(inc_path): |
|
for f in files: |
|
if f.endswith(".eo.h") or not f.endswith(".h"): |
|
continue |
|
open_args = (os.path.join(path, f),) |
|
open_kwargs = dict(mode="r") |
|
if sys.version_info[0] > 2: |
|
open_kwargs["encoding"] = "UTF-8" |
|
|
|
with open(*open_args, **open_kwargs) as header: |
|
capi = header.read() |
|
|
|
line_starts = [] |
|
i = 0 |
|
header.seek(0) |
|
for line in header: |
|
line_starts.append(i) |
|
i += len(line) |
|
matches = re.finditer(capi_pattern, capi) |
|
for match in matches: |
|
func = match.group(1) |
|
start = match.start() |
|
line_n = line_starts.index(start) + 1 |
|
capilns.append((f, line_n)) |
|
capis.append(func) |
|
|
|
return capilns, capis |
|
|
|
|
|
def get_pyapis(pxd_path, header_name, prefix): |
|
pyapilns = [] |
|
pyapis = [] |
|
pyapi_pattern1 = re.compile( |
|
'cdef extern from "' + header_name + '\.h":\n(.+)', |
|
flags=re.S |
|
) |
|
pyapi_pattern2 = re.compile( |
|
"^ +[a-zA-Z _*]+?(?!" + py_excludes + ")(" + prefix + "_\w+)\(", |
|
flags=re.M |
|
) |
|
|
|
for path, dirs, files in os.walk(pxd_path): |
|
for f in files: |
|
# if not f.endswith(".pxd"): |
|
# continue |
|
if f.endswith(".pyc"): |
|
continue |
|
open_args = (os.path.join(path, f),) |
|
open_kwargs = dict(mode="r") |
|
if sys.version_info[0] > 2: |
|
open_kwargs["encoding"] = "UTF-8" |
|
|
|
with open(*open_args, **open_kwargs) as pxd: |
|
pyapi = pxd.read() |
|
cdef = re.search(pyapi_pattern1, pyapi) |
|
if cdef: |
|
offset = cdef.start(1) |
|
line_starts = [] |
|
i = 0 |
|
pxd.seek(0) |
|
for line in pxd: |
|
line_starts.append(i) |
|
i += len(line) |
|
matches = re.finditer(pyapi_pattern2, cdef.group(1)) |
|
for match in matches: |
|
func = match.group(1) |
|
start = match.start() + offset |
|
line_n = line_starts.index(start) + 1 |
|
pyapilns.append((f, line_n)) |
|
pyapis.append(func) |
|
|
|
return pyapilns, pyapis |
|
|
|
|
|
rows, columns = os.popen('stty size', 'r').read().split() |
|
|
|
print( |
|
"{0:=^{columns}}".format( |
|
"Python-EFL API coverage report ", columns=columns |
|
) |
|
) |
|
print("") |
|
|
|
for lib in args.libs: |
|
|
|
inc_paths = pkg_config(lib) |
|
inc_path = None |
|
for p in inc_paths: |
|
if lib in p: |
|
inc_path = p[2:] |
|
break |
|
|
|
if inc_path is None: |
|
raise SystemExit |
|
|
|
pxd_path, header_name, prefix = params[lib] |
|
|
|
c_api_line_ns, c_apis = get_capis(inc_path, prefix) |
|
py_api_line_ns, py_apis = get_pyapis(pxd_path, header_name, prefix) |
|
|
|
capis = set(c_apis) |
|
pyapis = set(py_apis) |
|
differences = capis.union(pyapis) - capis.intersection(pyapis) |
|
|
|
if args.python: |
|
s = " %s API missing from Python-EFL " % (lib.capitalize()) |
|
print( |
|
"{0:-^{columns}}".format( |
|
s, columns=int(columns) |
|
) |
|
) |
|
for d in sorted(differences): |
|
try: |
|
i = c_apis.index(d) |
|
line_f, line_n = c_api_line_ns[i] |
|
except ValueError: |
|
pass |
|
else: |
|
print("{0:{third}}{1:5}: {2}".format( |
|
line_f, line_n, d, third=int(columns)/3 |
|
)) |
|
|
|
print("{0:-<{columns}}".format("", columns=columns)) |
|
print("") |
|
|
|
if args.c: |
|
s = " %s API in Python-EFL but not in C " % (lib.capitalize()) |
|
print( |
|
"{0:-^{columns}}".format( |
|
s, columns=int(columns) |
|
) |
|
) |
|
for d in sorted(differences): |
|
try: |
|
i = py_apis.index(d) |
|
line_f, line_n = py_api_line_ns[i] |
|
except ValueError: |
|
pass |
|
else: |
|
print("{0:20}{1:5}: {2}".format( |
|
line_f, line_n, d |
|
)) |
|
|
|
print("{0:-<{columns}}".format("", columns=columns)) |
|
print("") |
|
|
|
if args.python: |
|
print( |
|
"Number of functions missing from Python API: {0}".format( |
|
len(capis - capis.intersection(pyapis)) |
|
) |
|
) |
|
if args.c: |
|
print( |
|
"Number of functions missing from C API: {0}".format( |
|
len(pyapis - capis.intersection(pyapis)) |
|
) |
|
) |
|
|
|
if args.python or args.c: |
|
print("---") |
|
|
|
if args.python: |
|
print("Python API functions: {0}".format(len(pyapis))) |
|
if args.c: |
|
print("C API functions: {0}".format(len(capis))) |
|
|
|
if args.python and len(capis) > 0: |
|
percentage = \ |
|
float(len(capis.intersection(pyapis))) / \ |
|
float(len(capis)) * 100.0 |
|
print("===") |
|
print("Bindings coverage {0:.2f}%".format(percentage)) |
|
|
|
if args.python or args.c: |
|
print("---\n")
|
|
|