xref: /openbmc/openbmc/poky/scripts/contrib/bbvars.py (revision 73bd93f1)
1#!/usr/bin/env python3
2#
3# SPDX-License-Identifier: GPL-2.0-or-later
4#
5# Copyright (C) Darren Hart <dvhart@linux.intel.com>, 2010
6
7
8import sys
9import getopt
10import os
11import os.path
12import re
13
14# Set up sys.path to let us import tinfoil
15scripts_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
16lib_path = scripts_path + '/lib'
17sys.path.insert(0, lib_path)
18import scriptpath
19scriptpath.add_bitbake_lib_path()
20import bb.tinfoil
21
22def usage():
23    print('Usage: %s -d FILENAME [-d FILENAME]*' % os.path.basename(sys.argv[0]))
24    print('  -d FILENAME         documentation file to search')
25    print('  -h, --help          display this help and exit')
26    print('  -t FILENAME         documentation config file (for doc tags)')
27    print('  -T                  Only display variables with doc tags (requires -t)')
28
29def bbvar_is_documented(var, documented_vars):
30    ''' Check if variable (var) is in the list of documented variables(documented_vars) '''
31    if var in documented_vars:
32        return True
33    else:
34        return False
35
36def collect_documented_vars(docfiles):
37    ''' Walk the docfiles and collect the documented variables '''
38    documented_vars = []
39    prog = re.compile(r".*($|[^A-Z_])<glossentry id=\'var-")
40    var_prog = re.compile(r'<glossentry id=\'var-(.*)\'>')
41    for d in docfiles:
42        with open(d) as f:
43            documented_vars += var_prog.findall(f.read())
44
45    return documented_vars
46
47def bbvar_doctag(var, docconf):
48    prog = re.compile(r'^%s\[doc\] *= *"(.*)"' % (var))
49    if docconf == "":
50        return "?"
51
52    try:
53        f = open(docconf)
54    except IOError as err:
55        return err.args[1]
56
57    for line in f:
58        m = prog.search(line)
59        if m:
60            return m.group(1)
61
62    f.close()
63    return ""
64
65def main():
66    docfiles = []
67    bbvars = set()
68    undocumented = []
69    docconf = ""
70    onlydoctags = False
71
72    # Collect and validate input
73    try:
74        opts, args = getopt.getopt(sys.argv[1:], "d:hm:t:T", ["help"])
75    except getopt.GetoptError as err:
76        print('%s' % str(err))
77        usage()
78        sys.exit(2)
79
80    for o, a in opts:
81        if o in ('-h', '--help'):
82            usage()
83            sys.exit(0)
84        elif o == '-d':
85            if os.path.isfile(a):
86                docfiles.append(a)
87            else:
88                print('ERROR: documentation file %s is not a regular file' % a)
89                sys.exit(3)
90        elif o == "-t":
91            if os.path.isfile(a):
92                docconf = a
93        elif o == "-T":
94            onlydoctags = True
95        else:
96            assert False, "unhandled option"
97
98    if len(docfiles) == 0:
99        print('ERROR: no docfile specified')
100        usage()
101        sys.exit(5)
102
103    if onlydoctags and docconf == "":
104        print('ERROR: no docconf specified')
105        usage()
106        sys.exit(7)
107
108    prog = re.compile("^[^a-z]*$")
109    with bb.tinfoil.Tinfoil() as tinfoil:
110        tinfoil.prepare(config_only=False)
111        parser = bb.codeparser.PythonParser('parser', None)
112        datastore = tinfoil.config_data
113
114        def bbvars_update(data):
115            if prog.match(data):
116                bbvars.add(data)
117            if tinfoil.config_data.getVarFlag(data, 'python'):
118                try:
119                    parser.parse_python(tinfoil.config_data.getVar(data))
120                except bb.data_smart.ExpansionError:
121                    pass
122                for var in parser.references:
123                    if prog.match(var):
124                        bbvars.add(var)
125            else:
126                try:
127                    expandedVar = datastore.expandWithRefs(datastore.getVar(data, False), data)
128                    for var in expandedVar.references:
129                        if prog.match(var):
130                            bbvars.add(var)
131                except bb.data_smart.ExpansionError:
132                    pass
133
134        # Use tinfoil to collect all the variable names globally
135        for data in datastore:
136            bbvars_update(data)
137
138        # Collect variables from all recipes
139        for recipe in tinfoil.all_recipe_files(variants=False):
140            print("Checking %s" % recipe)
141            for data in tinfoil.parse_recipe_file(recipe):
142                bbvars_update(data)
143
144    documented_vars = collect_documented_vars(docfiles)
145
146    # Check each var for documentation
147    varlen = 0
148    for v in bbvars:
149        if len(v) > varlen:
150            varlen = len(v)
151        if not bbvar_is_documented(v, documented_vars):
152            undocumented.append(v)
153    undocumented.sort()
154    varlen = varlen + 1
155
156    # Report all undocumented variables
157    print('Found %d undocumented bb variables (out of %d):' % (len(undocumented), len(bbvars)))
158    header = '%s%s' % (str("VARIABLE").ljust(varlen), str("DOCTAG").ljust(7))
159    print(header)
160    print(str("").ljust(len(header), '='))
161    for v in undocumented:
162        doctag = bbvar_doctag(v, docconf)
163        if not onlydoctags or not doctag == "":
164            print('%s%s' % (v.ljust(varlen), doctag))
165
166
167if __name__ == "__main__":
168    main()
169