xref: /OK3568_Linux_fs/buildroot/utils/getdeveloperlib.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyunfrom io import open
2*4882a593Smuzhiyunimport os
3*4882a593Smuzhiyunimport re
4*4882a593Smuzhiyunimport glob
5*4882a593Smuzhiyunimport subprocess
6*4882a593Smuzhiyunimport sys
7*4882a593Smuzhiyunimport unittest
8*4882a593Smuzhiyun
9*4882a593Smuzhiyunbrpath = os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun#
12*4882a593Smuzhiyun# Patch parsing functions
13*4882a593Smuzhiyun#
14*4882a593Smuzhiyun
15*4882a593SmuzhiyunFIND_INFRA_IN_PATCH = re.compile(r"^\+\$\(eval \$\((host-)?([^-]*)-package\)\)$")
16*4882a593Smuzhiyun
17*4882a593Smuzhiyun
18*4882a593Smuzhiyundef analyze_patch(patch):
19*4882a593Smuzhiyun    """Parse one patch and return the list of files modified, added or
20*4882a593Smuzhiyun    removed by the patch."""
21*4882a593Smuzhiyun    files = set()
22*4882a593Smuzhiyun    infras = set()
23*4882a593Smuzhiyun    for line in patch:
24*4882a593Smuzhiyun        # If the patch is adding a package, find which infra it is
25*4882a593Smuzhiyun        m = FIND_INFRA_IN_PATCH.match(line)
26*4882a593Smuzhiyun        if m:
27*4882a593Smuzhiyun            infras.add(m.group(2))
28*4882a593Smuzhiyun        if not line.startswith("+++ "):
29*4882a593Smuzhiyun            continue
30*4882a593Smuzhiyun        line.strip()
31*4882a593Smuzhiyun        fname = line[line.find("/") + 1:].strip()
32*4882a593Smuzhiyun        if fname == "dev/null":
33*4882a593Smuzhiyun            continue
34*4882a593Smuzhiyun        files.add(fname)
35*4882a593Smuzhiyun    return (files, infras)
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun
38*4882a593SmuzhiyunFIND_INFRA_IN_MK = re.compile(r"^\$\(eval \$\((host-)?([^-]*)-package\)\)$")
39*4882a593Smuzhiyun
40*4882a593Smuzhiyun
41*4882a593Smuzhiyundef fname_get_package_infra(fname):
42*4882a593Smuzhiyun    """Checks whether the file name passed as argument is a Buildroot .mk
43*4882a593Smuzhiyun    file describing a package, and find the infrastructure it's using."""
44*4882a593Smuzhiyun    if not fname.endswith(".mk"):
45*4882a593Smuzhiyun        return None
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun    if not os.path.exists(fname):
48*4882a593Smuzhiyun        return None
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun    with open(fname, "r") as f:
51*4882a593Smuzhiyun        for line in f:
52*4882a593Smuzhiyun            line = line.strip()
53*4882a593Smuzhiyun            m = FIND_INFRA_IN_MK.match(line)
54*4882a593Smuzhiyun            if m:
55*4882a593Smuzhiyun                return m.group(2)
56*4882a593Smuzhiyun    return None
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun
59*4882a593Smuzhiyundef analyze_patches(patches):
60*4882a593Smuzhiyun    """Parse a list of patches and returns the list of files modified,
61*4882a593Smuzhiyun    added or removed by the patches, as well as the list of package
62*4882a593Smuzhiyun    infrastructures used by those patches (if any)"""
63*4882a593Smuzhiyun    allfiles = set()
64*4882a593Smuzhiyun    allinfras = set()
65*4882a593Smuzhiyun    for patch in patches:
66*4882a593Smuzhiyun        (files, infras) = analyze_patch(patch)
67*4882a593Smuzhiyun        allfiles = allfiles | files
68*4882a593Smuzhiyun        allinfras = allinfras | infras
69*4882a593Smuzhiyun    return (allfiles, allinfras)
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun#
73*4882a593Smuzhiyun# Unit-test parsing functions
74*4882a593Smuzhiyun#
75*4882a593Smuzhiyun
76*4882a593Smuzhiyundef get_all_test_cases(suite):
77*4882a593Smuzhiyun    """Generate all test-cases from a given test-suite.
78*4882a593Smuzhiyun    :return: (test.module, test.name)"""
79*4882a593Smuzhiyun    if issubclass(type(suite), unittest.TestSuite):
80*4882a593Smuzhiyun        for test in suite:
81*4882a593Smuzhiyun            for res in get_all_test_cases(test):
82*4882a593Smuzhiyun                yield res
83*4882a593Smuzhiyun    else:
84*4882a593Smuzhiyun        yield (suite.__module__, suite.__class__.__name__)
85*4882a593Smuzhiyun
86*4882a593Smuzhiyun
87*4882a593Smuzhiyundef list_unittests():
88*4882a593Smuzhiyun    """Use the unittest module to retreive all test cases from a given
89*4882a593Smuzhiyun    directory"""
90*4882a593Smuzhiyun    loader = unittest.TestLoader()
91*4882a593Smuzhiyun    suite = loader.discover(os.path.join(brpath, "support", "testing"))
92*4882a593Smuzhiyun    tests = {}
93*4882a593Smuzhiyun    for module, test in get_all_test_cases(suite):
94*4882a593Smuzhiyun        module_path = os.path.join("support", "testing", *module.split('.'))
95*4882a593Smuzhiyun        tests.setdefault(module_path, []).append('%s.%s' % (module, test))
96*4882a593Smuzhiyun    return tests
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun
99*4882a593Smuzhiyununittests = {}
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun#
103*4882a593Smuzhiyun# DEVELOPERS file parsing functions
104*4882a593Smuzhiyun#
105*4882a593Smuzhiyun
106*4882a593Smuzhiyunclass Developer:
107*4882a593Smuzhiyun    def __init__(self, name, files):
108*4882a593Smuzhiyun        self.name = name
109*4882a593Smuzhiyun        self.files = files
110*4882a593Smuzhiyun        self.packages = parse_developer_packages(files)
111*4882a593Smuzhiyun        self.architectures = parse_developer_architectures(files)
112*4882a593Smuzhiyun        self.infras = parse_developer_infras(files)
113*4882a593Smuzhiyun        self.runtime_tests = parse_developer_runtime_tests(files)
114*4882a593Smuzhiyun        self.defconfigs = parse_developer_defconfigs(files)
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun    def hasfile(self, f):
117*4882a593Smuzhiyun        for fs in self.files:
118*4882a593Smuzhiyun            if f.startswith(fs):
119*4882a593Smuzhiyun                return True
120*4882a593Smuzhiyun        return False
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun    def __repr__(self):
123*4882a593Smuzhiyun        name = '\'' + self.name.split(' <')[0][:20] + '\''
124*4882a593Smuzhiyun        things = []
125*4882a593Smuzhiyun        if len(self.files):
126*4882a593Smuzhiyun            things.append('{} files'.format(len(self.files)))
127*4882a593Smuzhiyun        if len(self.packages):
128*4882a593Smuzhiyun            things.append('{} pkgs'.format(len(self.packages)))
129*4882a593Smuzhiyun        if len(self.architectures):
130*4882a593Smuzhiyun            things.append('{} archs'.format(len(self.architectures)))
131*4882a593Smuzhiyun        if len(self.infras):
132*4882a593Smuzhiyun            things.append('{} infras'.format(len(self.infras)))
133*4882a593Smuzhiyun        if len(self.runtime_tests):
134*4882a593Smuzhiyun            things.append('{} tests'.format(len(self.runtime_tests)))
135*4882a593Smuzhiyun        if len(self.defconfigs):
136*4882a593Smuzhiyun            things.append('{} defconfigs'.format(len(self.defconfigs)))
137*4882a593Smuzhiyun        if things:
138*4882a593Smuzhiyun            return 'Developer <{} ({})>'.format(name, ', '.join(things))
139*4882a593Smuzhiyun        else:
140*4882a593Smuzhiyun            return 'Developer <' + name + '>'
141*4882a593Smuzhiyun
142*4882a593Smuzhiyun
143*4882a593Smuzhiyundef parse_developer_packages(fnames):
144*4882a593Smuzhiyun    """Given a list of file patterns, travel through the Buildroot source
145*4882a593Smuzhiyun    tree to find which packages are implemented by those file
146*4882a593Smuzhiyun    patterns, and return a list of those packages."""
147*4882a593Smuzhiyun    packages = set()
148*4882a593Smuzhiyun    for fname in fnames:
149*4882a593Smuzhiyun        for root, dirs, files in os.walk(os.path.join(brpath, fname)):
150*4882a593Smuzhiyun            for f in files:
151*4882a593Smuzhiyun                path = os.path.join(root, f)
152*4882a593Smuzhiyun                if fname_get_package_infra(path):
153*4882a593Smuzhiyun                    pkg = os.path.splitext(f)[0]
154*4882a593Smuzhiyun                    packages.add(pkg)
155*4882a593Smuzhiyun    return packages
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun
158*4882a593Smuzhiyundef parse_arches_from_config_in(fname):
159*4882a593Smuzhiyun    """Given a path to an arch/Config.in.* file, parse it to get the list
160*4882a593Smuzhiyun    of BR2_ARCH values for this architecture."""
161*4882a593Smuzhiyun    arches = set()
162*4882a593Smuzhiyun    with open(fname, "r") as f:
163*4882a593Smuzhiyun        parsing_arches = False
164*4882a593Smuzhiyun        for line in f:
165*4882a593Smuzhiyun            line = line.strip()
166*4882a593Smuzhiyun            if line == "config BR2_ARCH":
167*4882a593Smuzhiyun                parsing_arches = True
168*4882a593Smuzhiyun                continue
169*4882a593Smuzhiyun            if parsing_arches:
170*4882a593Smuzhiyun                m = re.match(r"^\s*default \"([^\"]*)\".*", line)
171*4882a593Smuzhiyun                if m:
172*4882a593Smuzhiyun                    arches.add(m.group(1))
173*4882a593Smuzhiyun                else:
174*4882a593Smuzhiyun                    parsing_arches = False
175*4882a593Smuzhiyun    return arches
176*4882a593Smuzhiyun
177*4882a593Smuzhiyun
178*4882a593Smuzhiyundef parse_developer_architectures(fnames):
179*4882a593Smuzhiyun    """Given a list of file names, find the ones starting by
180*4882a593Smuzhiyun    'arch/Config.in.', and use that to determine the architecture a
181*4882a593Smuzhiyun    developer is working on."""
182*4882a593Smuzhiyun    arches = set()
183*4882a593Smuzhiyun    for fname in fnames:
184*4882a593Smuzhiyun        if not re.match(r"^.*/arch/Config\.in\..*$", fname):
185*4882a593Smuzhiyun            continue
186*4882a593Smuzhiyun        arches = arches | parse_arches_from_config_in(fname)
187*4882a593Smuzhiyun    return arches
188*4882a593Smuzhiyun
189*4882a593Smuzhiyun
190*4882a593Smuzhiyundef parse_developer_infras(fnames):
191*4882a593Smuzhiyun    infras = set()
192*4882a593Smuzhiyun    for fname in fnames:
193*4882a593Smuzhiyun        m = re.match(r"^package/pkg-([^.]*).mk$", fname)
194*4882a593Smuzhiyun        if m:
195*4882a593Smuzhiyun            infras.add(m.group(1))
196*4882a593Smuzhiyun    return infras
197*4882a593Smuzhiyun
198*4882a593Smuzhiyun
199*4882a593Smuzhiyundef parse_developer_defconfigs(fnames):
200*4882a593Smuzhiyun    """Given a list of file names, returns the config names
201*4882a593Smuzhiyun    corresponding to defconfigs."""
202*4882a593Smuzhiyun    return {os.path.basename(fname[:-10])
203*4882a593Smuzhiyun            for fname in fnames
204*4882a593Smuzhiyun            if fname.endswith('_defconfig')}
205*4882a593Smuzhiyun
206*4882a593Smuzhiyun
207*4882a593Smuzhiyundef parse_developer_runtime_tests(fnames):
208*4882a593Smuzhiyun    """Given a list of file names, returns the runtime tests
209*4882a593Smuzhiyun    corresponding to the file."""
210*4882a593Smuzhiyun    all_files = []
211*4882a593Smuzhiyun    # List all files recursively
212*4882a593Smuzhiyun    for fname in fnames:
213*4882a593Smuzhiyun        if os.path.isdir(fname):
214*4882a593Smuzhiyun            for root, _dirs, files in os.walk(os.path.join(brpath, fname)):
215*4882a593Smuzhiyun                all_files += [os.path.join(root, f) for f in files]
216*4882a593Smuzhiyun        else:
217*4882a593Smuzhiyun            all_files.append(fname)
218*4882a593Smuzhiyun
219*4882a593Smuzhiyun    # Get all runtime tests
220*4882a593Smuzhiyun    runtimes = set()
221*4882a593Smuzhiyun    for f in all_files:
222*4882a593Smuzhiyun        name = os.path.splitext(f)[0]
223*4882a593Smuzhiyun        if name in unittests:
224*4882a593Smuzhiyun            runtimes |= set(unittests[name])
225*4882a593Smuzhiyun    return runtimes
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun
228*4882a593Smuzhiyundef parse_developers():
229*4882a593Smuzhiyun    """Parse the DEVELOPERS file and return a list of Developer objects."""
230*4882a593Smuzhiyun    developers = []
231*4882a593Smuzhiyun    linen = 0
232*4882a593Smuzhiyun    global unittests
233*4882a593Smuzhiyun    unittests = list_unittests()
234*4882a593Smuzhiyun    developers_fname = os.path.join(brpath, 'DEVELOPERS')
235*4882a593Smuzhiyun    with open(developers_fname, mode='r', encoding='utf_8') as f:
236*4882a593Smuzhiyun        files = []
237*4882a593Smuzhiyun        name = None
238*4882a593Smuzhiyun        for line in f:
239*4882a593Smuzhiyun            line = line.strip()
240*4882a593Smuzhiyun            if line.startswith("#"):
241*4882a593Smuzhiyun                continue
242*4882a593Smuzhiyun            elif line.startswith("N:"):
243*4882a593Smuzhiyun                if name is not None or len(files) != 0:
244*4882a593Smuzhiyun                    print("Syntax error in DEVELOPERS file, line %d" % linen,
245*4882a593Smuzhiyun                          file=sys.stderr)
246*4882a593Smuzhiyun                name = line[2:].strip()
247*4882a593Smuzhiyun            elif line.startswith("F:"):
248*4882a593Smuzhiyun                fname = line[2:].strip()
249*4882a593Smuzhiyun                dev_files = glob.glob(os.path.join(brpath, fname))
250*4882a593Smuzhiyun                if len(dev_files) == 0:
251*4882a593Smuzhiyun                    print("WARNING: '%s' doesn't match any file" % fname,
252*4882a593Smuzhiyun                          file=sys.stderr)
253*4882a593Smuzhiyun                for f in dev_files:
254*4882a593Smuzhiyun                    dev_file = os.path.relpath(f, brpath)
255*4882a593Smuzhiyun                    dev_file = dev_file.replace(os.sep, '/')  # force unix sep
256*4882a593Smuzhiyun                    files.append(dev_file)
257*4882a593Smuzhiyun            elif line == "":
258*4882a593Smuzhiyun                if not name:
259*4882a593Smuzhiyun                    continue
260*4882a593Smuzhiyun                developers.append(Developer(name, files))
261*4882a593Smuzhiyun                files = []
262*4882a593Smuzhiyun                name = None
263*4882a593Smuzhiyun            else:
264*4882a593Smuzhiyun                print("Syntax error in DEVELOPERS file, line %d: '%s'" % (linen, line),
265*4882a593Smuzhiyun                      file=sys.stderr)
266*4882a593Smuzhiyun                return None
267*4882a593Smuzhiyun            linen += 1
268*4882a593Smuzhiyun    # handle last developer
269*4882a593Smuzhiyun    if name is not None:
270*4882a593Smuzhiyun        developers.append(Developer(name, files))
271*4882a593Smuzhiyun    return developers
272*4882a593Smuzhiyun
273*4882a593Smuzhiyun
274*4882a593Smuzhiyundef check_developers(developers, basepath=None):
275*4882a593Smuzhiyun    """Look at the list of files versioned in Buildroot, and returns the
276*4882a593Smuzhiyun    list of files that are not handled by any developer"""
277*4882a593Smuzhiyun    if basepath is None:
278*4882a593Smuzhiyun        basepath = os.getcwd()
279*4882a593Smuzhiyun    cmd = ["git", "--git-dir", os.path.join(basepath, ".git"), "ls-files"]
280*4882a593Smuzhiyun    files = subprocess.check_output(cmd).decode(sys.stdout.encoding).strip().split("\n")
281*4882a593Smuzhiyun    unhandled_files = []
282*4882a593Smuzhiyun    for f in files:
283*4882a593Smuzhiyun        handled = False
284*4882a593Smuzhiyun        for d in developers:
285*4882a593Smuzhiyun            if d.hasfile(f):
286*4882a593Smuzhiyun                handled = True
287*4882a593Smuzhiyun                break
288*4882a593Smuzhiyun        if not handled:
289*4882a593Smuzhiyun            unhandled_files.append(f)
290*4882a593Smuzhiyun    return unhandled_files
291