]> rtime.felk.cvut.cz Git - coffee/buildroot.git/blob - utils/getdeveloperlib.py
genrandconfig: pass outputdir and buildrootdir as arguments
[coffee/buildroot.git] / utils / getdeveloperlib.py
1 import sys
2 import os
3 import re
4 import argparse
5 import glob
6 import subprocess
7
8 #
9 # Patch parsing functions
10 #
11
12 FIND_INFRA_IN_PATCH = re.compile("^\+\$\(eval \$\((host-)?([^-]*)-package\)\)$")
13
14 def analyze_patch(patch):
15     """Parse one patch and return the list of files modified, added or
16     removed by the patch."""
17     files = set()
18     infras = set()
19     for line in patch:
20         # If the patch is adding a package, find which infra it is
21         m = FIND_INFRA_IN_PATCH.match(line)
22         if m:
23             infras.add(m.group(2))
24         if not line.startswith("+++ "):
25             continue
26         line.strip()
27         fname = line[line.find("/") + 1 : ].strip()
28         if fname == "dev/null":
29             continue
30         files.add(fname)
31     return (files, infras)
32
33 FIND_INFRA_IN_MK = re.compile("^\$\(eval \$\((host-)?([^-]*)-package\)\)$")
34
35 def fname_get_package_infra(fname):
36     """Checks whether the file name passed as argument is a Buildroot .mk
37     file describing a package, and find the infrastructure it's using."""
38     if not fname.endswith(".mk"):
39         return None
40
41     if not os.path.exists(fname):
42         return None
43
44     with open(fname, "r") as f:
45         for l in f:
46             l = l.strip()
47             m = FIND_INFRA_IN_MK.match(l)
48             if m:
49                 return m.group(2)
50     return None
51
52 def get_infras(files):
53     """Search in the list of files for .mk files, and collect the package
54     infrastructures used by those .mk files."""
55     infras = set()
56     for fname in files:
57         infra = fname_get_package_infra(fname)
58         if infra:
59             infras.add(infra)
60     return infras
61
62 def analyze_patches(patches):
63     """Parse a list of patches and returns the list of files modified,
64     added or removed by the patches, as well as the list of package
65     infrastructures used by those patches (if any)"""
66     allfiles = set()
67     allinfras = set()
68     for patch in patches:
69         (files, infras) = analyze_patch(patch)
70         allfiles = allfiles | files
71         allinfras = allinfras | infras
72     allinfras = allinfras | get_infras(allfiles)
73     return (allfiles, allinfras)
74
75 #
76 # DEVELOPERS file parsing functions
77 #
78
79 class Developer:
80     def __init__(self, name, files):
81         self.name = name
82         self.files = files
83         self.packages = parse_developer_packages(files)
84         self.architectures = parse_developer_architectures(files)
85         self.infras = parse_developer_infras(files)
86
87     def hasfile(self, f):
88         f = os.path.abspath(f)
89         for fs in self.files:
90             if f.startswith(fs):
91                 return True
92         return False
93
94 def parse_developer_packages(fnames):
95     """Given a list of file patterns, travel through the Buildroot source
96     tree to find which packages are implemented by those file
97     patterns, and return a list of those packages."""
98     packages = set()
99     for fname in fnames:
100         for root, dirs, files in os.walk(fname):
101             for f in files:
102                 path = os.path.join(root, f)
103                 if fname_get_package_infra(path):
104                     pkg = os.path.splitext(f)[0]
105                     packages.add(pkg)
106     return packages
107
108 def parse_arches_from_config_in(fname):
109     """Given a path to an arch/Config.in.* file, parse it to get the list
110     of BR2_ARCH values for this architecture."""
111     arches = set()
112     with open(fname, "r") as f:
113         parsing_arches = False
114         for l in f:
115             l = l.strip()
116             if l == "config BR2_ARCH":
117                 parsing_arches = True
118                 continue
119             if parsing_arches:
120                 m = re.match("^\s*default \"([^\"]*)\".*", l)
121                 if m:
122                     arches.add(m.group(1))
123                 else:
124                     parsing_arches = False
125     return arches
126
127 def parse_developer_architectures(fnames):
128     """Given a list of file names, find the ones starting by
129     'arch/Config.in.', and use that to determine the architecture a
130     developer is working on."""
131     arches = set()
132     for fname in fnames:
133         if not re.match("^.*/arch/Config\.in\..*$", fname):
134             continue
135         arches = arches | parse_arches_from_config_in(fname)
136     return arches
137
138 def parse_developer_infras(fnames):
139     infras = set()
140     for fname in fnames:
141         m = re.match("^package/pkg-([^.]*).mk$", fname)
142         if m:
143             infras.add(m.group(1))
144     return infras
145
146 def parse_developers(basepath=None):
147     """Parse the DEVELOPERS file and return a list of Developer objects."""
148     developers = []
149     linen = 0
150     if basepath == None:
151         basepath = os.getcwd()
152     with open(os.path.join(basepath, "DEVELOPERS"), "r") as f:
153         files = []
154         name = None
155         for l in f:
156             l = l.strip()
157             if l.startswith("#"):
158                 continue
159             elif l.startswith("N:"):
160                 if name is not None or len(files) != 0:
161                     print("Syntax error in DEVELOPERS file, line %d" % linen)
162                 name = l[2:].strip()
163             elif l.startswith("F:"):
164                 fname = l[2:].strip()
165                 dev_files = glob.glob(os.path.join(basepath, fname))
166                 if len(dev_files) == 0:
167                     print("WARNING: '%s' doesn't match any file" % fname)
168                 files += dev_files
169             elif l == "":
170                 if not name:
171                     continue
172                 developers.append(Developer(name, files))
173                 files = []
174                 name = None
175             else:
176                 print("Syntax error in DEVELOPERS file, line %d: '%s'" % (linen, l))
177                 return None
178             linen += 1
179     # handle last developer
180     if name is not None:
181         developers.append(Developer(name, files))
182     return developers
183
184 def check_developers(developers, basepath=None):
185     """Look at the list of files versioned in Buildroot, and returns the
186     list of files that are not handled by any developer"""
187     if basepath == None:
188         basepath = os.getcwd()
189     cmd = ["git", "--git-dir", os.path.join(basepath, ".git"), "ls-files"]
190     files = subprocess.check_output(cmd).strip().split("\n")
191     unhandled_files = []
192     for f in files:
193         handled = False
194         for d in developers:
195             if d.hasfile(os.path.join(basepath, f)):
196                 handled = True
197                 break
198         if not handled:
199             unhandled_files.append(f)
200     return unhandled_files