diff options
author | Chris Larson <kergoth@openedhand.com> | 2006-08-21 00:50:19 +0000 |
---|---|---|
committer | Chris Larson <kergoth@openedhand.com> | 2006-08-21 00:50:19 +0000 |
commit | fe7c8c26ed4f207101bddaf7f1d03a782927da7b (patch) | |
tree | 637e5c90494541c7cad32fdcd397c59cb6b20c2e /meta/classes/patch.bbclass | |
parent | 4776ce09c45661d0ac5566c07e7cd8aa1d8302ed (diff) | |
download | poky-fe7c8c26ed4f207101bddaf7f1d03a782927da7b.tar.gz |
Changes for ticket:8, with fixes for the bugs reported by Richard.
git-svn-id: https://svn.o-hand.com/repos/poky/trunk@622 311d38ba-8fff-0310-9ca6-ca027cbcb966
Diffstat (limited to 'meta/classes/patch.bbclass')
-rw-r--r-- | meta/classes/patch.bbclass | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/meta/classes/patch.bbclass b/meta/classes/patch.bbclass new file mode 100644 index 0000000000..0f0d6a686f --- /dev/null +++ b/meta/classes/patch.bbclass | |||
@@ -0,0 +1,405 @@ | |||
1 | def patch_init(): | ||
2 | import os, sys | ||
3 | |||
4 | def md5sum(fname): | ||
5 | import md5, sys | ||
6 | |||
7 | f = file(fname, 'rb') | ||
8 | m = md5.new() | ||
9 | while True: | ||
10 | d = f.read(8096) | ||
11 | if not d: | ||
12 | break | ||
13 | m.update(d) | ||
14 | f.close() | ||
15 | return m.hexdigest() | ||
16 | |||
17 | class CmdError(Exception): | ||
18 | def __init__(self, exitstatus, output): | ||
19 | self.status = exitstatus | ||
20 | self.output = output | ||
21 | |||
22 | def __str__(self): | ||
23 | return "Command Error: exit status: %d Output:\n%s" % (self.status, self.output) | ||
24 | |||
25 | def runcmd(args, dir = None): | ||
26 | import commands | ||
27 | |||
28 | if dir: | ||
29 | olddir = os.path.abspath(os.curdir) | ||
30 | os.chdir(dir) | ||
31 | # print("cwd: %s -> %s" % (olddir, self.dir)) | ||
32 | |||
33 | try: | ||
34 | args = [ commands.mkarg(str(arg)) for arg in args ] | ||
35 | cmd = " ".join(args) | ||
36 | # print("cmd: %s" % cmd) | ||
37 | (exitstatus, output) = commands.getstatusoutput(cmd) | ||
38 | if exitstatus != 0: | ||
39 | raise CmdError(exitstatus >> 8, output) | ||
40 | return output | ||
41 | |||
42 | finally: | ||
43 | if dir: | ||
44 | os.chdir(olddir) | ||
45 | |||
46 | class PatchError(Exception): | ||
47 | def __init__(self, patch, msg): | ||
48 | self.msg = msg | ||
49 | self.patch = patch | ||
50 | |||
51 | def __str__(self): | ||
52 | return "Patch Error: patch %s: %s" % (os.path.basename(self.patch.file), self.msg) | ||
53 | |||
54 | import bb, bb.data, bb.fetch | ||
55 | |||
56 | class PatchSet(object): | ||
57 | defaults = { | ||
58 | "strippath": 1 | ||
59 | } | ||
60 | |||
61 | def __init__(self, dir, d): | ||
62 | self.dir = dir | ||
63 | self.d = d | ||
64 | self.patches = [] | ||
65 | self._current = None | ||
66 | |||
67 | def current(self): | ||
68 | return self._current | ||
69 | |||
70 | def Clean(self): | ||
71 | """ | ||
72 | Clean out the patch set. Generally includes unapplying all | ||
73 | patches and wiping out all associated metadata. | ||
74 | """ | ||
75 | raise NotImplementedError() | ||
76 | |||
77 | def Import(self, patch, force): | ||
78 | if not patch.get("file"): | ||
79 | if not patch.get("remote"): | ||
80 | raise PatchError("Patch file must be specified in patch import.") | ||
81 | else: | ||
82 | patch["file"] = bb.fetch.localpath(patch["remote"], self.d) | ||
83 | |||
84 | for param in PatchSet.defaults: | ||
85 | if not patch.get(param): | ||
86 | patch[param] = PatchSet.defaults[param] | ||
87 | |||
88 | if patch.get("remote"): | ||
89 | patch["file"] = bb.fetch.localpath(patch["remote"], self.d) | ||
90 | |||
91 | patch["filemd5"] = md5sum(patch["file"]) | ||
92 | |||
93 | def Push(self, force): | ||
94 | raise NotImplementedError() | ||
95 | |||
96 | def Pop(self, force): | ||
97 | raise NotImplementedError() | ||
98 | |||
99 | def Refresh(self, remote = None, all = None): | ||
100 | raise NotImplementedError() | ||
101 | |||
102 | |||
103 | class QuiltTree(PatchSet): | ||
104 | def _runcmd(self, args): | ||
105 | runcmd(["quilt"] + args, self.dir) | ||
106 | |||
107 | def _quiltpatchpath(self, file): | ||
108 | return os.path.join(self.dir, "patches", os.path.basename(file)) | ||
109 | |||
110 | |||
111 | def __init__(self, dir, d): | ||
112 | PatchSet.__init__(self, dir, d) | ||
113 | self.initialized = False | ||
114 | |||
115 | def Clean(self): | ||
116 | try: | ||
117 | self._runcmd(["pop", "-a", "-f"]) | ||
118 | except CmdError: | ||
119 | if sys.exc_value.output.strip() == "No patch removed": | ||
120 | pass | ||
121 | else: | ||
122 | raise PatchError("Unable to clean patches from tree:\n"+str(sys.exc_value)) | ||
123 | except OSError: | ||
124 | if str(sys.exc_value).startswith('OSError: [Errno 2]'): | ||
125 | pass | ||
126 | runcmd(["rm", "-rf", os.path.join(self.dir, "patches"), os.path.join(self.dir, ".pc")]) | ||
127 | self.initialized = True | ||
128 | |||
129 | def InitFromDir(self): | ||
130 | # read series -> self.patches | ||
131 | seriespath = os.path.join(self.dir, 'patches', 'series') | ||
132 | if not os.path.exists(self.dir): | ||
133 | raise Exception("Error: %s does not exist." % self.dir) | ||
134 | if os.path.exists(seriespath): | ||
135 | series = file(seriespath, 'r') | ||
136 | for line in series.readlines(): | ||
137 | patch = {} | ||
138 | parts = line.strip().split() | ||
139 | patch["quiltfile"] = self._quiltpatchpath(parts[0]) | ||
140 | patch["quiltfilemd5"] = md5sum(patch["quiltfile"]) | ||
141 | if len(parts) > 1: | ||
142 | patch["strippath"] = parts[1][2:] | ||
143 | self.patches.append(patch) | ||
144 | series.close() | ||
145 | |||
146 | # determine which patches are applied -> self._current | ||
147 | try: | ||
148 | output = runcmd(["quilt", "applied"], self.dir) | ||
149 | except CmdError: | ||
150 | if sys.exc_value.output.strip() == "No patches applied": | ||
151 | return | ||
152 | else: | ||
153 | raise sys.exc_value | ||
154 | output = [val for val in output.split('\n') if not val.startswith('#')] | ||
155 | for patch in self.patches: | ||
156 | if os.path.basename(patch["quiltfile"]) == output[-1]: | ||
157 | self._current = self.patches.index(patch) | ||
158 | self.initialized = True | ||
159 | |||
160 | def Import(self, patch, force = None): | ||
161 | if not self.initialized: | ||
162 | self.InitFromDir() | ||
163 | PatchSet.Import(self, patch, force) | ||
164 | |||
165 | args = ["import", "-p", patch["strippath"]] | ||
166 | if force: | ||
167 | args.append("-f") | ||
168 | args.append(patch["file"]) | ||
169 | |||
170 | self._runcmd(args) | ||
171 | |||
172 | patch["quiltfile"] = self._quiltpatchpath(patch["file"]) | ||
173 | patch["quiltfilemd5"] = md5sum(patch["quiltfile"]) | ||
174 | |||
175 | # TODO: determine if the file being imported: | ||
176 | # 1) is already imported, and is the same | ||
177 | # 2) is already imported, but differs | ||
178 | |||
179 | self.patches.insert(self._current or 0, patch) | ||
180 | |||
181 | |||
182 | def Push(self, force = None, all = None): | ||
183 | # quilt push [-f] | ||
184 | |||
185 | args = ["push"] | ||
186 | if force: | ||
187 | args.append("-f") | ||
188 | if all: | ||
189 | args.append("-a") | ||
190 | |||
191 | self._runcmd(args) | ||
192 | |||
193 | if self._current is not None: | ||
194 | self._current = self._current + 1 | ||
195 | else: | ||
196 | self._current = 0 | ||
197 | |||
198 | def Pop(self, force = None, all = None): | ||
199 | # quilt pop [-f] | ||
200 | args = ["pop"] | ||
201 | if force: | ||
202 | args.append("-f") | ||
203 | if all: | ||
204 | args.append("-a") | ||
205 | |||
206 | self._runcmd(args) | ||
207 | |||
208 | if self._current == 0: | ||
209 | self._current = None | ||
210 | |||
211 | if self._current is not None: | ||
212 | self._current = self._current - 1 | ||
213 | |||
214 | def Refresh(self, **kwargs): | ||
215 | if kwargs.get("remote"): | ||
216 | patch = self.patches[kwargs["patch"]] | ||
217 | if not patch: | ||
218 | raise PatchError("No patch found at index %s in patchset." % kwargs["patch"]) | ||
219 | (type, host, path, user, pswd, parm) = bb.decodeurl(patch["remote"]) | ||
220 | if type == "file": | ||
221 | import shutil | ||
222 | if not patch.get("file") and patch.get("remote"): | ||
223 | patch["file"] = bb.fetch.localpath(patch["remote"], self.d) | ||
224 | |||
225 | shutil.copyfile(patch["quiltfile"], patch["file"]) | ||
226 | else: | ||
227 | raise PatchError(patch, "Unable to do a remote refresh of %s, unsupported remote url scheme %s." % (os.path.basename(patch["quiltfile"]), type)) | ||
228 | else: | ||
229 | # quilt refresh | ||
230 | args = ["refresh"] | ||
231 | if kwargs.get("quiltfile"): | ||
232 | args.append(os.path.basename(kwargs["quiltfile"])) | ||
233 | elif kwargs.get("patch"): | ||
234 | args.append(os.path.basename(self.patches[kwargs["patch"]]["quiltfile"])) | ||
235 | self._runcmd(args) | ||
236 | |||
237 | class Resolver(object): | ||
238 | def __init__(self, patchset): | ||
239 | raise NotImplementedError() | ||
240 | |||
241 | def Resolve(self): | ||
242 | raise NotImplementedError() | ||
243 | |||
244 | def Revert(self): | ||
245 | raise NotImplementedError() | ||
246 | |||
247 | def Finalize(self): | ||
248 | raise NotImplementedError() | ||
249 | |||
250 | # Patch resolver which relies on the user doing all the work involved in the | ||
251 | # resolution, with the exception of refreshing the remote copy of the patch | ||
252 | # files (the urls). | ||
253 | class UserResolver(Resolver): | ||
254 | def __init__(self, patchset): | ||
255 | self.patchset = patchset | ||
256 | |||
257 | # Force a push in the patchset, then drop to a shell for the user to | ||
258 | # resolve any rejected hunks | ||
259 | def Resolve(self): | ||
260 | |||
261 | olddir = os.path.abspath(os.curdir) | ||
262 | os.chdir(self.patchset.dir) | ||
263 | try: | ||
264 | self.patchset.Push(True) | ||
265 | except CmdError, v: | ||
266 | # Patch application failed | ||
267 | if sys.exc_value.output.strip() == "No patches applied": | ||
268 | return | ||
269 | print(sys.exc_value) | ||
270 | print('NOTE: dropping user into a shell, so that patch rejects can be fixed manually.') | ||
271 | |||
272 | os.system('/bin/sh') | ||
273 | |||
274 | # Construct a new PatchSet after the user's changes, compare the | ||
275 | # sets, checking patches for modifications, and doing a remote | ||
276 | # refresh on each. | ||
277 | oldpatchset = self.patchset | ||
278 | self.patchset = oldpatchset.__class__(self.patchset.dir, self.patchset.d) | ||
279 | |||
280 | for patch in self.patchset.patches: | ||
281 | oldpatch = None | ||
282 | for opatch in oldpatchset.patches: | ||
283 | if opatch["quiltfile"] == patch["quiltfile"]: | ||
284 | oldpatch = opatch | ||
285 | |||
286 | if oldpatch: | ||
287 | patch["remote"] = oldpatch["remote"] | ||
288 | if patch["quiltfile"] == oldpatch["quiltfile"]: | ||
289 | if patch["quiltfilemd5"] != oldpatch["quiltfilemd5"]: | ||
290 | bb.note("Patch %s has changed, updating remote url %s" % (os.path.basename(patch["quiltfile"]), patch["remote"])) | ||
291 | # user change? remote refresh | ||
292 | self.patchset.Refresh(remote=True, patch=self.patchset.patches.index(patch)) | ||
293 | else: | ||
294 | # User did not fix the problem. Abort. | ||
295 | raise PatchError("Patch application failed, and user did not fix and refresh the patch.") | ||
296 | except Exception: | ||
297 | os.chdir(olddir) | ||
298 | raise | ||
299 | os.chdir(olddir) | ||
300 | |||
301 | # Throw away the changes to the patches in the patchset made by resolve() | ||
302 | def Revert(self): | ||
303 | raise NotImplementedError() | ||
304 | |||
305 | # Apply the changes to the patches in the patchset made by resolve() | ||
306 | def Finalize(self): | ||
307 | raise NotImplementedError() | ||
308 | |||
309 | g = globals() | ||
310 | g["PatchSet"] = PatchSet | ||
311 | g["QuiltTree"] = QuiltTree | ||
312 | g["Resolver"] = Resolver | ||
313 | g["UserResolver"] = UserResolver | ||
314 | |||
315 | addtask patch after do_unpack | ||
316 | do_patch[dirs] = "${WORKDIR}" | ||
317 | python base_do_patch() { | ||
318 | import re | ||
319 | import bb.fetch | ||
320 | |||
321 | patch_init() | ||
322 | |||
323 | src_uri = (bb.data.getVar('SRC_URI', d, 1) or '').split() | ||
324 | if not src_uri: | ||
325 | return | ||
326 | |||
327 | patchsetmap = { | ||
328 | "quilt": QuiltTree, | ||
329 | } | ||
330 | |||
331 | cls = patchsetmap[bb.data.getVar('PATCHTOOL', d, 1) or 'quilt'] | ||
332 | |||
333 | resolvermap = { | ||
334 | "user": UserResolver, | ||
335 | } | ||
336 | |||
337 | rcls = resolvermap[bb.data.getVar('PATCHRESOLVE', d, 1) or 'user'] | ||
338 | |||
339 | s = bb.data.getVar('S', d, 1) | ||
340 | |||
341 | patchset = cls(s, d) | ||
342 | patchset.Clean() | ||
343 | |||
344 | resolver = rcls(patchset) | ||
345 | |||
346 | workdir = bb.data.getVar('WORKDIR', d, 1) | ||
347 | for url in src_uri: | ||
348 | (type, host, path, user, pswd, parm) = bb.decodeurl(url) | ||
349 | if not "patch" in parm: | ||
350 | continue | ||
351 | |||
352 | bb.fetch.init([url],d) | ||
353 | url = bb.encodeurl((type, host, path, user, pswd, [])) | ||
354 | local = os.path.join('/', bb.fetch.localpath(url, d)) | ||
355 | |||
356 | # did it need to be unpacked? | ||
357 | dots = os.path.basename(local).split(".") | ||
358 | if dots[-1] in ['gz', 'bz2', 'Z']: | ||
359 | unpacked = os.path.join(bb.data.getVar('WORKDIR', d),'.'.join(dots[0:-1])) | ||
360 | else: | ||
361 | unpacked = local | ||
362 | unpacked = bb.data.expand(unpacked, d) | ||
363 | |||
364 | if "pnum" in parm: | ||
365 | pnum = parm["pnum"] | ||
366 | else: | ||
367 | pnum = "1" | ||
368 | |||
369 | if "pname" in parm: | ||
370 | pname = parm["pname"] | ||
371 | else: | ||
372 | pname = os.path.basename(unpacked) | ||
373 | |||
374 | if "mindate" in parm: | ||
375 | mindate = parm["mindate"] | ||
376 | else: | ||
377 | mindate = 0 | ||
378 | |||
379 | if "maxdate" in parm: | ||
380 | maxdate = parm["maxdate"] | ||
381 | else: | ||
382 | maxdate = "20711226" | ||
383 | |||
384 | pn = bb.data.getVar('PN', d, 1) | ||
385 | srcdate = bb.data.getVar('SRCDATE_%s' % pn, d, 1) | ||
386 | |||
387 | if not srcdate: | ||
388 | srcdate = bb.data.getVar('SRCDATE', d, 1) | ||
389 | |||
390 | if srcdate == "now": | ||
391 | srcdate = bb.data.getVar('DATE', d, 1) | ||
392 | |||
393 | if (maxdate < srcdate) or (mindate > srcdate): | ||
394 | if (maxdate < srcdate): | ||
395 | bb.note("Patch '%s' is outdated" % pname) | ||
396 | |||
397 | if (mindate > srcdate): | ||
398 | bb.note("Patch '%s' is predated" % pname) | ||
399 | |||
400 | continue | ||
401 | |||
402 | bb.note("Applying patch '%s'" % pname) | ||
403 | patchset.Import({"file":unpacked, "remote":url, "strippath": pnum}, True) | ||
404 | resolver.Resolve() | ||
405 | } | ||