1 class NotFoundError(Exception):
2 def __init__(self
, path
):
6 return "Error: %s not found." % self
.path
8 class CmdError(Exception):
9 def __init__(self
, exitstatus
, output
):
10 self
.status
= exitstatus
14 return "Command Error: exit status: %d Output:\n%s" % (self
.status
, self
.output
)
17 def runcmd(args
, dir = None):
21 olddir
= os
.path
.abspath(os
.curdir
)
22 if not os
.path
.exists(dir):
23 raise NotFoundError(dir)
25 # print("cwd: %s -> %s" % (olddir, dir))
28 args
= [ commands
.mkarg(str(arg
)) for arg
in args
]
30 # print("cmd: %s" % cmd)
31 (exitstatus
, output
) = commands
.getstatusoutput(cmd
)
33 raise CmdError(exitstatus
>> 8, output
)
40 class PatchError(Exception):
41 def __init__(self
, msg
):
45 return "Patch Error: %s" % self
.msg
47 class PatchSet(object):
52 def __init__(self
, dir, d
):
63 Clean out the patch set. Generally includes unapplying all
64 patches and wiping out all associated metadata.
66 raise NotImplementedError()
68 def Import(self
, patch
, force
):
69 if not patch
.get("file"):
70 if not patch
.get("remote"):
71 raise PatchError("Patch file must be specified in patch import.")
73 patch
["file"] = bb
.fetch
.localpath(patch
["remote"], self
.d
)
75 for param
in PatchSet
.defaults
:
76 if not patch
.get(param
):
77 patch
[param
] = PatchSet
.defaults
[param
]
79 if patch
.get("remote"):
80 patch
["file"] = bb
.data
.expand(bb
.fetch
.localpath(patch
["remote"], self
.d
), self
.d
)
82 patch
["filemd5"] = bb
.utils
.md5_file(patch
["file"])
84 def Push(self
, force
):
85 raise NotImplementedError()
88 raise NotImplementedError()
90 def Refresh(self
, remote
= None, all
= None):
91 raise NotImplementedError()
94 class PatchTree(PatchSet
):
95 def __init__(self
, dir, d
):
96 PatchSet
.__init
__(self
, dir, d
)
98 def Import(self
, patch
, force
= None):
100 PatchSet
.Import(self
, patch
, force
)
102 if self
._current
is not None:
103 i
= self
._current
+ 1
106 self
.patches
.insert(i
, patch
)
108 def _applypatch(self
, patch
, force
= False, reverse
= False, run
= True):
109 shellcmd
= ["cat", patch
['file'], "|", "patch", "-p", patch
['strippath']]
111 shellcmd
.append('-R')
114 return "sh" + "-c" + " ".join(shellcmd
)
117 shellcmd
.append('--dry-run')
119 output
= runcmd(["sh", "-c", " ".join(shellcmd
)], self
.dir)
124 shellcmd
.pop(len(shellcmd
) - 1)
125 output
= runcmd(["sh", "-c", " ".join(shellcmd
)], self
.dir)
128 def Push(self
, force
= False, all
= False, run
= True):
130 for i
in self
.patches
:
131 if self
._current
is not None:
132 self
._current
= self
._current
+ 1
135 self
._applypatch
(i
, force
)
137 if self
._current
is not None:
138 self
._current
= self
._current
+ 1
141 return self
._applypatch
(self
.patches
[self
._current
], force
)
144 def Pop(self
, force
= None, all
= None):
146 for i
in self
.patches
:
147 self
._applypatch
(i
, force
, True)
149 self
._applypatch
(self
.patches
[self
._current
], force
, True)
154 class GitApplyTree(PatchTree
):
155 def __init__(self
, dir, d
):
156 PatchTree
.__init
__(self
, dir, d
)
158 def _applypatch(self
, patch
, force
= False, reverse
= False, run
= True):
159 shellcmd
= ["git", "--git-dir=.", "apply", "-p%s" % patch
['strippath']]
162 shellcmd
.append('-R')
164 shellcmd
.append(patch
['file'])
167 return "sh" + "-c" + " ".join(shellcmd
)
169 return runcmd(["sh", "-c", " ".join(shellcmd
)], self
.dir)
172 class QuiltTree(PatchSet
):
173 def _runcmd(self
, args
, run
= True):
174 quiltrc
= bb
.data
.getVar('QUILTRCFILE', self
.d
, 1)
176 return ["quilt"] + ["--quiltrc"] + [quiltrc
] + args
177 runcmd(["quilt"] + ["--quiltrc"] + [quiltrc
] + args
, self
.dir)
179 def _quiltpatchpath(self
, file):
180 return os
.path
.join(self
.dir, "patches", os
.path
.basename(file))
183 def __init__(self
, dir, d
):
184 PatchSet
.__init
__(self
, dir, d
)
185 self
.initialized
= False
186 p
= os
.path
.join(self
.dir, 'patches')
187 if not os
.path
.exists(p
):
192 self
._runcmd
(["pop", "-a", "-f"])
195 self
.initialized
= True
197 def InitFromDir(self
):
198 # read series -> self.patches
199 seriespath
= os
.path
.join(self
.dir, 'patches', 'series')
200 if not os
.path
.exists(self
.dir):
201 raise Exception("Error: %s does not exist." % self
.dir)
202 if os
.path
.exists(seriespath
):
203 series
= file(seriespath
, 'r')
204 for line
in series
.readlines():
206 parts
= line
.strip().split()
207 patch
["quiltfile"] = self
._quiltpatchpath
(parts
[0])
208 patch
["quiltfilemd5"] = bb
.utils
.md5_file(patch
["quiltfile"])
210 patch
["strippath"] = parts
[1][2:]
211 self
.patches
.append(patch
)
214 # determine which patches are applied -> self._current
216 output
= runcmd(["quilt", "applied"], self
.dir)
219 if sys
.exc_value
.output
.strip() == "No patches applied":
223 output
= [val
for val
in output
.split('\n') if not val
.startswith('#')]
224 for patch
in self
.patches
:
225 if os
.path
.basename(patch
["quiltfile"]) == output
[-1]:
226 self
._current
= self
.patches
.index(patch
)
227 self
.initialized
= True
229 def Import(self
, patch
, force
= None):
230 if not self
.initialized
:
232 PatchSet
.Import(self
, patch
, force
)
233 os
.symlink(patch
["file"], self
._quiltpatchpath
(patch
["file"]))
234 f
= open(os
.path
.join(self
.dir, "patches","series"), "a");
235 f
.write(os
.path
.basename(patch
["file"]) + " -p" + patch
["strippath"]+"\n")
237 patch
["quiltfile"] = self
._quiltpatchpath
(patch
["file"])
238 patch
["quiltfilemd5"] = bb
.utils
.md5_file(patch
["quiltfile"])
240 # TODO: determine if the file being imported:
241 # 1) is already imported, and is the same
242 # 2) is already imported, but differs
244 self
.patches
.insert(self
._current
or 0, patch
)
247 def Push(self
, force
= False, all
= False, run
= True):
256 return self
._runcmd
(args
, run
)
260 if self
._current
is not None:
261 self
._current
= self
._current
+ 1
265 def Pop(self
, force
= None, all
= None):
275 if self
._current
== 0:
278 if self
._current
is not None:
279 self
._current
= self
._current
- 1
281 def Refresh(self
, **kwargs
):
282 if kwargs
.get("remote"):
283 patch
= self
.patches
[kwargs
["patch"]]
285 raise PatchError("No patch found at index %s in patchset." % kwargs
["patch"])
286 (type, host
, path
, user
, pswd
, parm
) = bb
.decodeurl(patch
["remote"])
289 if not patch
.get("file") and patch
.get("remote"):
290 patch
["file"] = bb
.fetch
.localpath(patch
["remote"], self
.d
)
292 shutil
.copyfile(patch
["quiltfile"], patch
["file"])
294 raise PatchError("Unable to do a remote refresh of %s, unsupported remote url scheme %s." % (os
.path
.basename(patch
["quiltfile"]), type))
298 if kwargs
.get("quiltfile"):
299 args
.append(os
.path
.basename(kwargs
["quiltfile"]))
300 elif kwargs
.get("patch"):
301 args
.append(os
.path
.basename(self
.patches
[kwargs
["patch"]]["quiltfile"]))
304 class Resolver(object):
305 def __init__(self
, patchset
):
306 raise NotImplementedError()
309 raise NotImplementedError()
312 raise NotImplementedError()
315 raise NotImplementedError()
317 class NOOPResolver(Resolver
):
318 def __init__(self
, patchset
):
319 self
.patchset
= patchset
322 olddir
= os
.path
.abspath(os
.curdir
)
323 os
.chdir(self
.patchset
.dir)
331 # Patch resolver which relies on the user doing all the work involved in the
332 # resolution, with the exception of refreshing the remote copy of the patch
334 class UserResolver(Resolver
):
335 def __init__(self
, patchset
):
336 self
.patchset
= patchset
338 # Force a push in the patchset, then drop to a shell for the user to
339 # resolve any rejected hunks
342 olddir
= os
.path
.abspath(os
.curdir
)
343 os
.chdir(self
.patchset
.dir)
345 self
.patchset
.Push(False)
347 # Patch application failed
348 patchcmd
= self
.patchset
.Push(True, False, False)
350 t
= bb
.data
.getVar('T', d
, 1)
352 bb
.msg
.fatal(bb
.msg
.domain
.Build
, "T not set")
355 rcfile
= "%s/bashrc.%s.%s" % (t
, str(os
.getpid()), random
.random())
356 f
= open(rcfile
, "w")
357 f
.write("echo '*** Manual patch resolution mode ***'\n")
358 f
.write("echo 'Dropping to a shell, so patch rejects can be fixed manually.'\n")
359 f
.write("echo 'Run \"quilt refresh\" when patch is corrected, press CTRL+D to exit.'\n")
361 f
.write(" ".join(patchcmd
) + "\n")
362 f
.write("#" + bb
.data
.getVar('TERMCMDRUN', d
, 1))
364 os
.chmod(rcfile
, 0775)
366 os
.environ
['TERMWINDOWTITLE'] = "Bitbake: Please fix patch rejects manually"
367 os
.environ
['TERMRCFILE'] = rcfile
368 rc
= os
.system(bb
.data
.getVar('TERMCMDRUN', d
, 1))
369 if os
.WIFEXITED(rc
) and os
.WEXITSTATUS(rc
) != 0:
370 bb
.msg
.fatal(bb
.msg
.domain
.Build
, ("Cannot proceed with manual patch resolution - '%s' not found. " \
371 + "Check TERMCMDRUN variable.") % bb
.data
.getVar('TERMCMDRUN', d
, 1))
373 # Construct a new PatchSet after the user's changes, compare the
374 # sets, checking patches for modifications, and doing a remote
376 oldpatchset
= self
.patchset
377 self
.patchset
= oldpatchset
.__class
__(self
.patchset
.dir, self
.patchset
.d
)
379 for patch
in self
.patchset
.patches
:
381 for opatch
in oldpatchset
.patches
:
382 if opatch
["quiltfile"] == patch
["quiltfile"]:
386 patch
["remote"] = oldpatch
["remote"]
387 if patch
["quiltfile"] == oldpatch
["quiltfile"]:
388 if patch
["quiltfilemd5"] != oldpatch
["quiltfilemd5"]:
389 bb
.note("Patch %s has changed, updating remote url %s" % (os
.path
.basename(patch
["quiltfile"]), patch
["remote"]))
390 # user change? remote refresh
391 self
.patchset
.Refresh(remote
=True, patch
=self
.patchset
.patches
.index(patch
))
393 # User did not fix the problem. Abort.
394 raise PatchError("Patch application failed, and user did not fix and refresh the patch.")