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