xf86-video-omapfb: pandora: handle cycle/forcer events better
[openembedded.git] / classes / patch.bbclass
1 # Copyright (C) 2006  OpenedHand LTD
2
3 # Point to an empty file so any user's custom settings don't break things
4 QUILTRCFILE ?= "${STAGING_BINDIR_NATIVE}/quiltrc"
5
6 def patch_init(d):
7         class NotFoundError(Exception):
8                 def __init__(self, path):
9                         self.path = path
10                 def __str__(self):
11                         return "Error: %s not found." % self.path
12
13         def md5sum(fname):
14                 # when we move to Python 2.5 as minimal supported
15                 # we can kill that try/except as hashlib is 2.5+
16                 try:
17                         import hashlib
18                         m = hashlib.md5()
19                 except ImportError:
20                         import md5
21                         m = md5.new()
22
23                 try:
24                         f = file(fname, 'rb')
25                 except IOError:
26                         raise NotFoundError(fname)
27
28                 while True:
29                         d = f.read(8096)
30                         if not d:
31                                 break
32                         m.update(d)
33                 f.close()
34                 return m.hexdigest()
35
36         class CmdError(Exception):
37                 def __init__(self, exitstatus, output):
38                         self.status = exitstatus
39                         self.output = output
40
41                 def __str__(self):
42                         return "Command Error: exit status: %d  Output:\n%s" % (self.status, self.output)
43
44
45         def runcmd(args, dir = None):
46                 import commands
47
48                 if dir:
49                         olddir = os.path.abspath(os.curdir)
50                         if not os.path.exists(dir):
51                                 raise NotFoundError(dir)
52                         os.chdir(dir)
53                         # print("cwd: %s -> %s" % (olddir, dir))
54
55                 try:
56                         args = [ commands.mkarg(str(arg)) for arg in args ]
57                         cmd = " ".join(args)
58                         # print("cmd: %s" % cmd)
59                         (exitstatus, output) = commands.getstatusoutput(cmd)
60                         if exitstatus != 0:
61                                 raise CmdError(exitstatus >> 8, output)
62                         return output
63
64                 finally:
65                         if dir:
66                                 os.chdir(olddir)
67
68         class PatchError(Exception):
69                 def __init__(self, msg):
70                         self.msg = msg
71
72                 def __str__(self):
73                         return "Patch Error: %s" % self.msg
74
75         class PatchSet(object):
76                 defaults = {
77                         "strippath": 1
78                 }
79
80                 def __init__(self, dir, d):
81                         self.dir = dir
82                         self.d = d
83                         self.patches = []
84                         self._current = None
85
86                 def current(self):
87                         return self._current
88
89                 def Clean(self):
90                         """
91                         Clean out the patch set.  Generally includes unapplying all
92                         patches and wiping out all associated metadata.
93                         """
94                         raise NotImplementedError()
95
96                 def Import(self, patch, force):
97                         if not patch.get("file"):
98                                 if not patch.get("remote"):
99                                         raise PatchError("Patch file must be specified in patch import.")
100                                 else:
101                                         patch["file"] = bb.fetch.localpath(patch["remote"], self.d)
102
103                         for param in PatchSet.defaults:
104                                 if not patch.get(param):
105                                         patch[param] = PatchSet.defaults[param]
106
107                         if patch.get("remote"):
108                                 patch["file"] = bb.data.expand(bb.fetch.localpath(patch["remote"], self.d), self.d)
109
110                         patch["filemd5"] = md5sum(patch["file"])
111
112                 def Push(self, force):
113                         raise NotImplementedError()
114
115                 def Pop(self, force):
116                         raise NotImplementedError()
117
118                 def Refresh(self, remote = None, all = None):
119                         raise NotImplementedError()
120
121
122         class PatchTree(PatchSet):
123                 def __init__(self, dir, d):
124                         PatchSet.__init__(self, dir, d)
125
126                 def Import(self, patch, force = None):
127                         """"""
128                         PatchSet.Import(self, patch, force)
129
130                         if self._current is not None:
131                                 i = self._current + 1
132                         else:
133                                 i = 0
134                         self.patches.insert(i, patch)
135
136                 def _applypatch(self, patch, force = False, reverse = False, run = True):
137                         shellcmd = ["cat", patch['file'], "|", "patch", "-p", patch['strippath']]
138                         if reverse:
139                                 shellcmd.append('-R')
140
141                         if not run:
142                                 return "sh" + "-c" + " ".join(shellcmd)
143
144                         if not force:
145                                 shellcmd.append('--dry-run')
146
147                         output = runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
148
149                         if force:
150                                 return
151
152                         shellcmd.pop(len(shellcmd) - 1)
153                         output = runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
154                         return output
155
156                 def Push(self, force = False, all = False, run = True):
157                         bb.note("self._current is %s" % self._current)
158                         bb.note("patches is %s" % self.patches)
159                         if all:
160                                 for i in self.patches:
161                                         if self._current is not None:
162                                                 self._current = self._current + 1
163                                         else:
164                                                 self._current = 0
165                                         bb.note("applying patch %s" % i)
166                                         self._applypatch(i, force)
167                         else:
168                                 if self._current is not None:
169                                         self._current = self._current + 1
170                                 else:
171                                         self._current = 0
172                                 bb.note("applying patch %s" % self.patches[self._current])
173                                 return self._applypatch(self.patches[self._current], force)
174
175
176                 def Pop(self, force = None, all = None):
177                         if all:
178                                 for i in self.patches:
179                                         self._applypatch(i, force, True)
180                         else:
181                                 self._applypatch(self.patches[self._current], force, True)
182
183                 def Clean(self):
184                         """"""
185
186         class GitApplyTree(PatchTree):
187                 def __init__(self, dir, d):
188                         PatchTree.__init__(self, dir, d)
189
190                 def _applypatch(self, patch, force = False, reverse = False, run = True):
191                         shellcmd = ["git", "--git-dir=.", "apply", "-p%s" % patch['strippath']]
192
193                         if reverse:
194                                 shellcmd.append('-R')
195
196                         shellcmd.append(patch['file'])
197
198                         if not run:
199                                 return "sh" + "-c" + " ".join(shellcmd)
200
201                         return runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
202
203
204         class QuiltTree(PatchSet):
205                 def _runcmd(self, args, run = True):
206                         quiltrc = bb.data.getVar('QUILTRCFILE', self.d, 1)
207                         if not run:
208                                 return ["quilt"] + ["--quiltrc"] + [quiltrc] + args
209                         runcmd(["quilt"] + ["--quiltrc"] + [quiltrc] + args, self.dir)
210
211                 def _quiltpatchpath(self, file):
212                         return os.path.join(self.dir, "patches", os.path.basename(file))
213
214
215                 def __init__(self, dir, d):
216                         PatchSet.__init__(self, dir, d)
217                         self.initialized = False
218                         p = os.path.join(self.dir, 'patches')
219                         if not os.path.exists(p):
220                                 os.makedirs(p)
221
222                 def Clean(self):
223                         try:
224                                 self._runcmd(["pop", "-a", "-f"])
225                         except Exception:
226                                 pass
227                         self.initialized = True
228
229                 def InitFromDir(self):
230                         # read series -> self.patches
231                         seriespath = os.path.join(self.dir, 'patches', 'series')
232                         if not os.path.exists(self.dir):
233                                 raise Exception("Error: %s does not exist." % self.dir)
234                         if os.path.exists(seriespath):
235                                 series = file(seriespath, 'r')
236                                 for line in series.readlines():
237                                         patch = {}
238                                         parts = line.strip().split()
239                                         patch["quiltfile"] = self._quiltpatchpath(parts[0])
240                                         patch["quiltfilemd5"] = md5sum(patch["quiltfile"])
241                                         if len(parts) > 1:
242                                                 patch["strippath"] = parts[1][2:]
243                                         self.patches.append(patch)
244                                 series.close()
245
246                                 # determine which patches are applied -> self._current
247                                 try:
248                                         output = runcmd(["quilt", "applied"], self.dir)
249                                 except CmdError:
250                                         import sys
251                                         if sys.exc_value.output.strip() == "No patches applied":
252                                                 return
253                                         else:
254                                                 raise sys.exc_value
255                                 output = [val for val in output.split('\n') if not val.startswith('#')]
256                                 for patch in self.patches:
257                                         if os.path.basename(patch["quiltfile"]) == output[-1]:
258                                                 self._current = self.patches.index(patch)
259                         self.initialized = True
260
261                 def Import(self, patch, force = None):
262                         if not self.initialized:
263                                 self.InitFromDir()
264                         PatchSet.Import(self, patch, force)
265
266                         args = ["import", "-p", patch["strippath"]]
267                         if force:
268                                 args.append("-f")
269                                 args.append("-dn")
270                         args.append(patch["file"])
271
272                         self._runcmd(args)
273
274                         patch["quiltfile"] = self._quiltpatchpath(patch["file"])
275                         patch["quiltfilemd5"] = md5sum(patch["quiltfile"])
276
277                         # TODO: determine if the file being imported:
278                         #          1) is already imported, and is the same
279                         #          2) is already imported, but differs
280
281                         self.patches.insert(self._current or 0, patch)
282
283
284                 def Push(self, force = False, all = False, run = True):
285                         # quilt push [-f]
286
287                         args = ["push"]
288                         if force:
289                                 args.append("-f")
290                         if all:
291                                 args.append("-a")
292                         if not run:
293                                 return self._runcmd(args, run)
294
295                         self._runcmd(args)
296
297                         if self._current is not None:
298                                 self._current = self._current + 1
299                         else:
300                                 self._current = 0
301
302                 def Pop(self, force = None, all = None):
303                         # quilt pop [-f]
304                         args = ["pop"]
305                         if force:
306                                 args.append("-f")
307                         if all:
308                                 args.append("-a")
309
310                         self._runcmd(args)
311
312                         if self._current == 0:
313                                 self._current = None
314
315                         if self._current is not None:
316                                 self._current = self._current - 1
317
318                 def Refresh(self, **kwargs):
319                         if kwargs.get("remote"):
320                                 patch = self.patches[kwargs["patch"]]
321                                 if not patch:
322                                         raise PatchError("No patch found at index %s in patchset." % kwargs["patch"])
323                                 (type, host, path, user, pswd, parm) = bb.decodeurl(patch["remote"])
324                                 if type == "file":
325                                         import shutil
326                                         if not patch.get("file") and patch.get("remote"):
327                                                 patch["file"] = bb.fetch.localpath(patch["remote"], self.d)
328
329                                         shutil.copyfile(patch["quiltfile"], patch["file"])
330                                 else:
331                                         raise PatchError("Unable to do a remote refresh of %s, unsupported remote url scheme %s." % (os.path.basename(patch["quiltfile"]), type))
332                         else:
333                                 # quilt refresh
334                                 args = ["refresh"]
335                                 if kwargs.get("quiltfile"):
336                                         args.append(os.path.basename(kwargs["quiltfile"]))
337                                 elif kwargs.get("patch"):
338                                         args.append(os.path.basename(self.patches[kwargs["patch"]]["quiltfile"]))
339                                 self._runcmd(args)
340
341         class Resolver(object):
342                 def __init__(self, patchset):
343                         raise NotImplementedError()
344
345                 def Resolve(self):
346                         raise NotImplementedError()
347
348                 def Revert(self):
349                         raise NotImplementedError()
350
351                 def Finalize(self):
352                         raise NotImplementedError()
353
354         class NOOPResolver(Resolver):
355                 def __init__(self, patchset):
356                         self.patchset = patchset
357
358                 def Resolve(self):
359                         olddir = os.path.abspath(os.curdir)
360                         os.chdir(self.patchset.dir)
361                         try:
362                                 self.patchset.Push()
363                         except Exception:
364                                 import sys
365                                 os.chdir(olddir)
366                                 raise sys.exc_value
367
368         # Patch resolver which relies on the user doing all the work involved in the
369         # resolution, with the exception of refreshing the remote copy of the patch
370         # files (the urls).
371         class UserResolver(Resolver):
372                 def __init__(self, patchset):
373                         self.patchset = patchset
374
375                 # Force a push in the patchset, then drop to a shell for the user to
376                 # resolve any rejected hunks
377                 def Resolve(self):
378
379                         olddir = os.path.abspath(os.curdir)
380                         os.chdir(self.patchset.dir)
381                         try:
382                                 self.patchset.Push(False)
383                         except CmdError, v:
384                                 # Patch application failed
385                                 patchcmd = self.patchset.Push(True, False, False)
386  
387                                 t = bb.data.getVar('T', d, 1)
388                                 if not t:
389                                         bb.msg.fatal(bb.msg.domain.Build, "T not set")
390                                 bb.mkdirhier(t)
391                                 import random
392                                 rcfile = "%s/bashrc.%s.%s" % (t, str(os.getpid()), random.random())
393                                 f = open(rcfile, "w")
394                                 f.write("echo '*** Manual patch resolution mode ***'\n")
395                                 f.write("echo 'Dropping to a shell, so patch rejects can be fixed manually.'\n")
396                                 f.write("echo 'Run \"quilt refresh\" when patch is corrected, press CTRL+D to exit.'\n")
397                                 f.write("echo ''\n")
398                                 f.write(" ".join(patchcmd) + "\n")
399                                 f.write("#" + bb.data.getVar('TERMCMDRUN', d, 1))
400                                 f.close()
401                                 os.chmod(rcfile, 0775)
402  
403                                 os.environ['TERMWINDOWTITLE'] = "Bitbake: Please fix patch rejects manually"
404                                 os.environ['TERMRCFILE'] = rcfile
405                                 rc = os.system(bb.data.getVar('TERMCMDRUN', d, 1))
406                                 if os.WIFEXITED(rc) and os.WEXITSTATUS(rc) != 0:
407                                         bb.msg.fatal(bb.msg.domain.Build, ("Cannot proceed with manual patch resolution - '%s' not found. " \
408                                             + "Check TERMCMDRUN variable.") % bb.data.getVar('TERMCMDRUN', d, 1))
409
410                                 # Construct a new PatchSet after the user's changes, compare the
411                                 # sets, checking patches for modifications, and doing a remote
412                                 # refresh on each.
413                                 oldpatchset = self.patchset
414                                 self.patchset = oldpatchset.__class__(self.patchset.dir, self.patchset.d)
415
416                                 for patch in self.patchset.patches:
417                                         oldpatch = None
418                                         for opatch in oldpatchset.patches:
419                                                 if opatch["quiltfile"] == patch["quiltfile"]:
420                                                         oldpatch = opatch
421
422                                         if oldpatch:
423                                                 patch["remote"] = oldpatch["remote"]
424                                                 if patch["quiltfile"] == oldpatch["quiltfile"]:
425                                                         if patch["quiltfilemd5"] != oldpatch["quiltfilemd5"]:
426                                                                 bb.note("Patch %s has changed, updating remote url %s" % (os.path.basename(patch["quiltfile"]), patch["remote"]))
427                                                                 # user change?  remote refresh
428                                                                 self.patchset.Refresh(remote=True, patch=self.patchset.patches.index(patch))
429                                                         else:
430                                                                 # User did not fix the problem.  Abort.
431                                                                 raise PatchError("Patch application failed, and user did not fix and refresh the patch.")
432                         except Exception:
433                                 os.chdir(olddir)
434                                 raise
435                         os.chdir(olddir)
436
437         g = globals()
438         g["PatchSet"] = PatchSet
439         g["PatchTree"] = PatchTree
440         g["QuiltTree"] = QuiltTree
441         g["GitApplyTree"] = GitApplyTree
442         g["Resolver"] = Resolver
443         g["UserResolver"] = UserResolver
444         g["NOOPResolver"] = NOOPResolver
445         g["NotFoundError"] = NotFoundError
446         g["CmdError"] = CmdError
447
448 addtask patch after do_unpack
449 do_patch[dirs] = "${WORKDIR}"
450
451 PATCHDEPENDENCY = "${PATCHTOOL}-native:do_populate_staging"
452 do_patch[depends] = "${PATCHDEPENDENCY}"
453
454 python patch_do_patch() {
455         patch_init(d)
456
457         src_uri = (bb.data.getVar('SRC_URI', d, 1) or '').split()
458         if not src_uri:
459                 return
460
461         patchsetmap = {
462                 "patch": PatchTree,
463                 "quilt": QuiltTree,
464                 "git": GitApplyTree,
465         }
466
467         cls = patchsetmap[bb.data.getVar('PATCHTOOL', d, 1) or 'quilt']
468
469         resolvermap = {
470                 "noop": NOOPResolver,
471                 "user": UserResolver,
472         }
473
474         rcls = resolvermap[bb.data.getVar('PATCHRESOLVE', d, 1) or 'user']
475
476         s = bb.data.getVar('S', d, 1)
477
478         path = os.getenv('PATH')
479         os.putenv('PATH', bb.data.getVar('PATH', d, 1))
480         patchset = cls(s, d)
481         patchset.Clean()
482
483         resolver = rcls(patchset)
484
485         workdir = bb.data.getVar('WORKDIR', d, 1)
486         for url in src_uri:
487                 (type, host, path, user, pswd, parm) = bb.decodeurl(url)
488                 if not "patch" in parm:
489                         continue
490
491                 bb.fetch.init([url],d)
492                 url = bb.encodeurl((type, host, path, user, pswd, []))
493                 local = os.path.join('/', bb.fetch.localpath(url, d))
494
495                 # did it need to be unpacked?
496                 dots = os.path.basename(local).split(".")
497                 if dots[-1] in ['gz', 'bz2', 'Z']:
498                         unpacked = os.path.join(bb.data.getVar('WORKDIR', d),'.'.join(dots[0:-1]))
499                 else:
500                         unpacked = local
501                 unpacked = bb.data.expand(unpacked, d)
502
503                 if "pnum" in parm:
504                         pnum = parm["pnum"]
505                 else:
506                         pnum = "1"
507
508                 if "pname" in parm:
509                         pname = parm["pname"]
510                 else:
511                         pname = os.path.basename(unpacked)
512
513                 if "mindate" in parm or "maxdate" in parm:
514                         pn = bb.data.getVar('PN', d, 1)
515                         srcdate = bb.data.getVar('SRCDATE_%s' % pn, d, 1)
516                         if not srcdate:
517                                 srcdate = bb.data.getVar('SRCDATE', d, 1)
518
519                         if srcdate == "now":
520                                 srcdate = bb.data.getVar('DATE', d, 1)
521
522                         if "maxdate" in parm and parm["maxdate"] < srcdate:
523                                 bb.note("Patch '%s' is outdated" % pname)
524                                 continue
525
526                         if "mindate" in parm and parm["mindate"] > srcdate:
527                                 bb.note("Patch '%s' is predated" % pname)
528                                 continue
529
530
531                 if "minrev" in parm:
532                         srcrev = bb.data.getVar('SRCREV', d, 1)
533                         if srcrev and srcrev < parm["minrev"]:
534                                 bb.note("Patch '%s' applies to later revisions" % pname)
535                                 continue
536
537                 if "maxrev" in parm:
538                         srcrev = bb.data.getVar('SRCREV', d, 1)         
539                         if srcrev and srcrev > parm["maxrev"]:
540                                 bb.note("Patch '%s' applies to earlier revisions" % pname)
541                                 continue
542
543                 bb.note("Applying patch '%s' (%s)" % (pname, base_path_out(unpacked, d)))
544                 try:
545                         patchset.Import({"file":unpacked, "remote":url, "strippath": pnum}, True)
546                 except:
547                         import sys
548                         raise bb.build.FuncFailed(str(sys.exc_value))
549                 resolver.Resolve()
550 }
551
552 EXPORT_FUNCTIONS do_patch