Skip to content

Commit 6ff1f62

Browse files
committed
Zephyr driver scraper: Add initial version
Initial version of utility for scraping Zephyr driver information. Signed-off-by: David Leach <[email protected]>
1 parent 8d1aa11 commit 6ff1f62

File tree

1 file changed

+162
-0
lines changed

1 file changed

+162
-0
lines changed

scripts/zephyr_driver.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright 2025 NXP
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
import logging
8+
import glob
9+
import os
10+
import argparse
11+
from zephyr_repo_tools.api_scan import scan_api_structs
12+
from zephyr_repo_tools.driver_info import ZephyrDriver
13+
import pandas as pd
14+
from pathlib import Path
15+
from openpyxl.utils import get_column_letter
16+
from openpyxl.formatting.rule import FormulaRule
17+
from openpyxl.styles import PatternFill
18+
from collections import defaultdict
19+
20+
21+
def configure_logging(verbosity: int) -> None:
22+
"""
23+
Configure logging level based on verbosity count.
24+
-v -> INFO
25+
-vv -> DEBUG
26+
"""
27+
if verbosity == 0:
28+
level = logging.WARNING
29+
elif verbosity == 1:
30+
level = logging.INFO
31+
else:
32+
level = logging.DEBUG
33+
34+
logging.basicConfig(level=level, format="%(levelname)s: %(message)s")
35+
36+
37+
def find_driver_files(patterns, compat_only=False, recursive=False, zephyr_def=None):
38+
entries = []
39+
40+
# If recursive flag is set but pattern doesn't use **, modify patterns
41+
if recursive:
42+
patterns = [p.replace('*/', '**/') if '**' not in p else p for p in patterns]
43+
44+
found_files = set()
45+
46+
# Process each pattern
47+
for pattern in patterns:
48+
# Find matching files
49+
logging.debug(f"pattern: {pattern}")
50+
matching_files = glob.glob(pattern, recursive=True)
51+
52+
# Process each file
53+
for file in matching_files:
54+
if file in found_files:
55+
logging.debug(f"duplicate file: {file}")
56+
continue
57+
58+
logging.debug(f"Adding file: {file}")
59+
60+
found_files.add(file)
61+
62+
# Get absolute path
63+
file_path = os.path.abspath(file)
64+
65+
driver_prop = ZephyrDriver(filepath=file_path)
66+
67+
# Skip if we only want files with compat values
68+
if compat_only and not driver_prop.dt_compats:
69+
continue
70+
71+
try:
72+
if zephyr_def:
73+
driver_prop.load_zephyr_def(zephyr_def.get(driver_prop.api_struct,[]))
74+
except Exception as e:
75+
logging.debug(f"\nError driver_prop {driver_prop}: {e}")
76+
77+
# Add to our entries list
78+
entries.append(driver_prop)
79+
80+
return entries
81+
82+
def export_to_xlsx(output_xlsx, driver_list):
83+
#
84+
# create a spreadsheet where each tab is a unique driver class output:
85+
# driver_class, filename, dt_compat, PM, API structure, api1, api2, api3...
86+
# where:
87+
# each API is either true/false. Later, someone can edit the spreadsheet and
88+
# use N/A if the API function doesn't make sense for the IP
89+
#
90+
91+
# Get unique list of classes found in driver_list
92+
class_groups = defaultdict(list)
93+
for driver_item in driver_list:
94+
key = driver_item.driver_class
95+
class_groups[key].append(driver_item)
96+
97+
green = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid")
98+
#yellow = PatternFill(start_color="FFEB9C", end_color="FFEB9C", fill_type="solid")
99+
red = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid")
100+
101+
with pd.ExcelWriter(output_xlsx, mode="w", engine="openpyxl") as writer:
102+
for class_key, objs in sorted(
103+
class_groups.items(),
104+
key=lambda kv: (kv[0] is not None, "" if kv[0] is None else kv[0].casefold())
105+
):
106+
df = pd.DataFrame([o.api_list() for o in objs])
107+
sheet = "None" if class_key is None else str(class_key)[:31]
108+
df.to_excel(writer, sheet_name=sheet, index=False)
109+
ws = writer.sheets[sheet]
110+
for col in ws.columns:
111+
max_length = 0
112+
column = get_column_letter(col[0].column)
113+
for cell in col:
114+
try:
115+
value = cell.value
116+
if isinstance(value, bool):
117+
value ="TRUE" if value else "FALSE"
118+
if value:
119+
max_length = max(max_length, len(str(value)))
120+
except (AttributeError, TypeError):
121+
pass
122+
# add 4 so text isn't right against the boarder
123+
ws.column_dimensions[column].width = max_length + 6
124+
125+
# Freeze first row and turn on filters for all columns
126+
ws.freeze_panes = "B2"
127+
ws.auto_filter.ref = ws.dimensions
128+
129+
data_range = f"A2:{ws.cell(row=ws.max_row, column=ws.max_column).coordinate}"
130+
ws.conditional_formatting.add(data_range,
131+
FormulaRule(formula=['ISNUMBER(SEARCH("TRUE",A2))'], fill=green))
132+
ws.conditional_formatting.add(data_range,
133+
FormulaRule(formula=['ISNUMBER(SEARCH("FALSE",A2))'], fill=red))
134+
135+
def main():
136+
parser = argparse.ArgumentParser(description="Scrape driver properties")
137+
parser.add_argument('patterns', nargs='+', help='One or more wildcard patterns (e.g., "drivers/*/*mcux*.c")')
138+
parser.add_argument('-i', '--include', type=Path, help='Scan for Zephyr __subsystem API structs (zephyr/include)')
139+
parser.add_argument('-r', '--recursive', action='store_true', help='Force recursive search for patterns')
140+
parser.add_argument('-c', '--compat-only', action='store_true',help='Include only files with DT_DRV_COMPAT values')
141+
parser.add_argument('-x', '--xlsx', help='Output data to xlsx file')
142+
parser.add_argument('-v', '--verbose', action="count", default=0, help="Increase log verbosity (-v for INFO, -vv for DEBUG).",)
143+
144+
args = parser.parse_args()
145+
146+
configure_logging(args.verbose)
147+
logging.info("Starting driver processing...")
148+
logging.debug("Arguments: %s", args)
149+
150+
151+
zephyr_def = None
152+
if args.include:
153+
zephyr_def = scan_api_structs(args.include)
154+
logging.info(f"Scanning API information from {args.include}")
155+
156+
driver_list = find_driver_files(args.patterns,compat_only=args.compat_only,recursive=args.recursive,zephyr_def=zephyr_def)
157+
158+
if args.xlsx:
159+
export_to_xlsx(args.xlsx, driver_list)
160+
161+
if __name__ == "__main__":
162+
main()

0 commit comments

Comments
 (0)