]>
Commit | Line | Data |
---|---|---|
1fbcd2ca | 1 | import oe.path |
cefbf3a1 CL |
2 | import os |
3 | import bb.utils, bb.msg, bb.data, bb.fetch2 | |
1fbcd2ca | 4 | |
ac023d77 JL |
5 | class NotFoundError(Exception): |
6 | def __init__(self, path): | |
7 | self.path = path | |
8 | ||
9 | def __str__(self): | |
10 | return "Error: %s not found." % self.path | |
11 | ||
12 | class CmdError(Exception): | |
13 | def __init__(self, exitstatus, output): | |
14 | self.status = exitstatus | |
15 | self.output = output | |
16 | ||
17 | def __str__(self): | |
18 | return "Command Error: exit status: %d Output:\n%s" % (self.status, self.output) | |
19 | ||
20 | ||
21 | def runcmd(args, dir = None): | |
22 | import commands | |
23 | ||
24 | if dir: | |
25 | olddir = os.path.abspath(os.curdir) | |
26 | if not os.path.exists(dir): | |
27 | raise NotFoundError(dir) | |
28 | os.chdir(dir) | |
29 | # print("cwd: %s -> %s" % (olddir, dir)) | |
30 | ||
31 | try: | |
32 | args = [ commands.mkarg(str(arg)) for arg in args ] | |
33 | cmd = " ".join(args) | |
34 | # print("cmd: %s" % cmd) | |
35 | (exitstatus, output) = commands.getstatusoutput(cmd) | |
36 | if exitstatus != 0: | |
37 | raise CmdError(exitstatus >> 8, output) | |
38 | return output | |
39 | ||
40 | finally: | |
41 | if dir: | |
42 | os.chdir(olddir) | |
43 | ||
44 | class PatchError(Exception): | |
45 | def __init__(self, msg): | |
46 | self.msg = msg | |
47 | ||
48 | def __str__(self): | |
49 | return "Patch Error: %s" % self.msg | |
50 | ||
51 | class PatchSet(object): | |
52 | defaults = { | |
53 | "strippath": 1 | |
54 | } | |
55 | ||
56 | def __init__(self, dir, d): | |
57 | self.dir = dir | |
58 | self.d = d | |
59 | self.patches = [] | |
60 | self._current = None | |
61 | ||
62 | def current(self): | |
63 | return self._current | |
64 | ||
65 | def Clean(self): | |
66 | """ | |
67 | Clean out the patch set. Generally includes unapplying all | |
68 | patches and wiping out all associated metadata. | |
69 | """ | |
70 | raise NotImplementedError() | |
71 | ||
72 | def Import(self, patch, force): | |
73 | if not patch.get("file"): | |
74 | if not patch.get("remote"): | |
75 | raise PatchError("Patch file must be specified in patch import.") | |
76 | else: | |
984e90f4 | 77 | patch["file"] = bb.fetch2.localpath(patch["remote"], self.d) |
ac023d77 JL |
78 | |
79 | for param in PatchSet.defaults: | |
80 | if not patch.get(param): | |
81 | patch[param] = PatchSet.defaults[param] | |
82 | ||
83 | if patch.get("remote"): | |
984e90f4 | 84 | patch["file"] = bb.data.expand(bb.fetch2.localpath(patch["remote"], self.d), self.d) |
ac023d77 JL |
85 | |
86 | patch["filemd5"] = bb.utils.md5_file(patch["file"]) | |
87 | ||
88 | def Push(self, force): | |
89 | raise NotImplementedError() | |
90 | ||
91 | def Pop(self, force): | |
92 | raise NotImplementedError() | |
93 | ||
94 | def Refresh(self, remote = None, all = None): | |
95 | raise NotImplementedError() | |
96 | ||
97 | ||
98 | class PatchTree(PatchSet): | |
99 | def __init__(self, dir, d): | |
100 | PatchSet.__init__(self, dir, d) | |
101 | ||
102 | def Import(self, patch, force = None): | |
103 | """""" | |
104 | PatchSet.Import(self, patch, force) | |
105 | ||
106 | if self._current is not None: | |
107 | i = self._current + 1 | |
108 | else: | |
109 | i = 0 | |
110 | self.patches.insert(i, patch) | |
111 | ||
112 | def _applypatch(self, patch, force = False, reverse = False, run = True): | |
113 | shellcmd = ["cat", patch['file'], "|", "patch", "-p", patch['strippath']] | |
114 | if reverse: | |
115 | shellcmd.append('-R') | |
116 | ||
117 | if not run: | |
118 | return "sh" + "-c" + " ".join(shellcmd) | |
119 | ||
120 | if not force: | |
121 | shellcmd.append('--dry-run') | |
122 | ||
123 | output = runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) | |
124 | ||
125 | if force: | |
126 | return | |
127 | ||
128 | shellcmd.pop(len(shellcmd) - 1) | |
129 | output = runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) | |
130 | return output | |
131 | ||
132 | def Push(self, force = False, all = False, run = True): | |
133 | bb.note("self._current is %s" % self._current) | |
134 | bb.note("patches is %s" % self.patches) | |
135 | if all: | |
136 | for i in self.patches: | |
137 | if self._current is not None: | |
138 | self._current = self._current + 1 | |
139 | else: | |
140 | self._current = 0 | |
141 | bb.note("applying patch %s" % i) | |
142 | self._applypatch(i, force) | |
143 | else: | |
144 | if self._current is not None: | |
145 | self._current = self._current + 1 | |
146 | else: | |
147 | self._current = 0 | |
148 | bb.note("applying patch %s" % self.patches[self._current]) | |
149 | return self._applypatch(self.patches[self._current], force) | |
150 | ||
151 | ||
152 | def Pop(self, force = None, all = None): | |
153 | if all: | |
154 | for i in self.patches: | |
155 | self._applypatch(i, force, True) | |
156 | else: | |
157 | self._applypatch(self.patches[self._current], force, True) | |
158 | ||
159 | def Clean(self): | |
160 | """""" | |
161 | ||
162 | class GitApplyTree(PatchTree): | |
163 | def __init__(self, dir, d): | |
164 | PatchTree.__init__(self, dir, d) | |
165 | ||
166 | def _applypatch(self, patch, force = False, reverse = False, run = True): | |
167 | shellcmd = ["git", "--git-dir=.", "apply", "-p%s" % patch['strippath']] | |
168 | ||
169 | if reverse: | |
170 | shellcmd.append('-R') | |
171 | ||
172 | shellcmd.append(patch['file']) | |
173 | ||
174 | if not run: | |
175 | return "sh" + "-c" + " ".join(shellcmd) | |
176 | ||
177 | return runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) | |
178 | ||
179 | ||
180 | class QuiltTree(PatchSet): | |
181 | def _runcmd(self, args, run = True): | |
182 | quiltrc = bb.data.getVar('QUILTRCFILE', self.d, 1) | |
183 | if not run: | |
184 | return ["quilt"] + ["--quiltrc"] + [quiltrc] + args | |
185 | runcmd(["quilt"] + ["--quiltrc"] + [quiltrc] + args, self.dir) | |
186 | ||
187 | def _quiltpatchpath(self, file): | |
188 | return os.path.join(self.dir, "patches", os.path.basename(file)) | |
189 | ||
190 | ||
191 | def __init__(self, dir, d): | |
192 | PatchSet.__init__(self, dir, d) | |
193 | self.initialized = False | |
194 | p = os.path.join(self.dir, 'patches') | |
195 | if not os.path.exists(p): | |
196 | os.makedirs(p) | |
197 | ||
198 | def Clean(self): | |
199 | try: | |
200 | self._runcmd(["pop", "-a", "-f"]) | |
201 | except Exception: | |
202 | pass | |
203 | self.initialized = True | |
204 | ||
205 | def InitFromDir(self): | |
206 | # read series -> self.patches | |
207 | seriespath = os.path.join(self.dir, 'patches', 'series') | |
208 | if not os.path.exists(self.dir): | |
209 | raise Exception("Error: %s does not exist." % self.dir) | |
210 | if os.path.exists(seriespath): | |
211 | series = file(seriespath, 'r') | |
212 | for line in series.readlines(): | |
213 | patch = {} | |
214 | parts = line.strip().split() | |
215 | patch["quiltfile"] = self._quiltpatchpath(parts[0]) | |
216 | patch["quiltfilemd5"] = bb.utils.md5_file(patch["quiltfile"]) | |
217 | if len(parts) > 1: | |
218 | patch["strippath"] = parts[1][2:] | |
219 | self.patches.append(patch) | |
220 | series.close() | |
221 | ||
222 | # determine which patches are applied -> self._current | |
223 | try: | |
224 | output = runcmd(["quilt", "applied"], self.dir) | |
225 | except CmdError: | |
226 | import sys | |
227 | if sys.exc_value.output.strip() == "No patches applied": | |
228 | return | |
229 | else: | |
230 | raise sys.exc_value | |
231 | output = [val for val in output.split('\n') if not val.startswith('#')] | |
232 | for patch in self.patches: | |
233 | if os.path.basename(patch["quiltfile"]) == output[-1]: | |
234 | self._current = self.patches.index(patch) | |
235 | self.initialized = True | |
236 | ||
237 | def Import(self, patch, force = None): | |
238 | if not self.initialized: | |
239 | self.InitFromDir() | |
240 | PatchSet.Import(self, patch, force) | |
1fbcd2ca JL |
241 | oe.path.symlink(patch["file"], self._quiltpatchpath(patch["file"])) |
242 | f = open(os.path.join(self.dir, "patches","series"), "a"); | |
243 | f.write(os.path.basename(patch["file"]) + " -p" + patch["strippath"]+"\n") | |
244 | f.close() | |
ac023d77 JL |
245 | patch["quiltfile"] = self._quiltpatchpath(patch["file"]) |
246 | patch["quiltfilemd5"] = bb.utils.md5_file(patch["quiltfile"]) | |
247 | ||
248 | # TODO: determine if the file being imported: | |
249 | # 1) is already imported, and is the same | |
250 | # 2) is already imported, but differs | |
251 | ||
252 | self.patches.insert(self._current or 0, patch) | |
253 | ||
254 | ||
255 | def Push(self, force = False, all = False, run = True): | |
256 | # quilt push [-f] | |
257 | ||
258 | args = ["push"] | |
259 | if force: | |
260 | args.append("-f") | |
261 | if all: | |
262 | args.append("-a") | |
263 | if not run: | |
264 | return self._runcmd(args, run) | |
265 | ||
266 | self._runcmd(args) | |
267 | ||
268 | if self._current is not None: | |
269 | self._current = self._current + 1 | |
270 | else: | |
271 | self._current = 0 | |
272 | ||
273 | def Pop(self, force = None, all = None): | |
274 | # quilt pop [-f] | |
275 | args = ["pop"] | |
276 | if force: | |
277 | args.append("-f") | |
278 | if all: | |
279 | args.append("-a") | |
280 | ||
281 | self._runcmd(args) | |
282 | ||
283 | if self._current == 0: | |
284 | self._current = None | |
285 | ||
286 | if self._current is not None: | |
287 | self._current = self._current - 1 | |
288 | ||
289 | def Refresh(self, **kwargs): | |
290 | if kwargs.get("remote"): | |
291 | patch = self.patches[kwargs["patch"]] | |
292 | if not patch: | |
293 | raise PatchError("No patch found at index %s in patchset." % kwargs["patch"]) | |
294 | (type, host, path, user, pswd, parm) = bb.decodeurl(patch["remote"]) | |
295 | if type == "file": | |
296 | import shutil | |
297 | if not patch.get("file") and patch.get("remote"): | |
984e90f4 | 298 | patch["file"] = bb.fetch2.localpath(patch["remote"], self.d) |
ac023d77 JL |
299 | |
300 | shutil.copyfile(patch["quiltfile"], patch["file"]) | |
301 | else: | |
302 | raise PatchError("Unable to do a remote refresh of %s, unsupported remote url scheme %s." % (os.path.basename(patch["quiltfile"]), type)) | |
303 | else: | |
304 | # quilt refresh | |
305 | args = ["refresh"] | |
306 | if kwargs.get("quiltfile"): | |
307 | args.append(os.path.basename(kwargs["quiltfile"])) | |
308 | elif kwargs.get("patch"): | |
309 | args.append(os.path.basename(self.patches[kwargs["patch"]]["quiltfile"])) | |
310 | self._runcmd(args) | |
311 | ||
312 | class Resolver(object): | |
313 | def __init__(self, patchset): | |
314 | raise NotImplementedError() | |
315 | ||
316 | def Resolve(self): | |
317 | raise NotImplementedError() | |
318 | ||
319 | def Revert(self): | |
320 | raise NotImplementedError() | |
321 | ||
322 | def Finalize(self): | |
323 | raise NotImplementedError() | |
324 | ||
325 | class NOOPResolver(Resolver): | |
326 | def __init__(self, patchset): | |
327 | self.patchset = patchset | |
328 | ||
329 | def Resolve(self): | |
330 | olddir = os.path.abspath(os.curdir) | |
331 | os.chdir(self.patchset.dir) | |
332 | try: | |
333 | self.patchset.Push() | |
334 | except Exception: | |
335 | import sys | |
336 | os.chdir(olddir) | |
337 | raise sys.exc_value | |
338 | ||
339 | # Patch resolver which relies on the user doing all the work involved in the | |
340 | # resolution, with the exception of refreshing the remote copy of the patch | |
341 | # files (the urls). | |
342 | class UserResolver(Resolver): | |
343 | def __init__(self, patchset): | |
344 | self.patchset = patchset | |
345 | ||
346 | # Force a push in the patchset, then drop to a shell for the user to | |
347 | # resolve any rejected hunks | |
348 | def Resolve(self): | |
349 | ||
350 | olddir = os.path.abspath(os.curdir) | |
351 | os.chdir(self.patchset.dir) | |
352 | try: | |
353 | self.patchset.Push(False) | |
354 | except CmdError, v: | |
355 | # Patch application failed | |
356 | patchcmd = self.patchset.Push(True, False, False) | |
357 | ||
1de1ac2e | 358 | t = bb.data.getVar('T', self.patchset.d, 1) |
ac023d77 JL |
359 | if not t: |
360 | bb.msg.fatal(bb.msg.domain.Build, "T not set") | |
361 | bb.mkdirhier(t) | |
362 | import random | |
363 | rcfile = "%s/bashrc.%s.%s" % (t, str(os.getpid()), random.random()) | |
364 | f = open(rcfile, "w") | |
365 | f.write("echo '*** Manual patch resolution mode ***'\n") | |
366 | f.write("echo 'Dropping to a shell, so patch rejects can be fixed manually.'\n") | |
367 | f.write("echo 'Run \"quilt refresh\" when patch is corrected, press CTRL+D to exit.'\n") | |
368 | f.write("echo ''\n") | |
369 | f.write(" ".join(patchcmd) + "\n") | |
1de1ac2e | 370 | f.write("#" + bb.data.getVar('TERMCMDRUN', self.patchset.d, 1)) |
ac023d77 JL |
371 | f.close() |
372 | os.chmod(rcfile, 0775) | |
373 | ||
374 | os.environ['TERMWINDOWTITLE'] = "Bitbake: Please fix patch rejects manually" | |
beef002b | 375 | os.environ['SHELLCMDS'] = "bash --rcfile " + rcfile |
1de1ac2e | 376 | rc = os.system(bb.data.getVar('TERMCMDRUN', self.patchset.d, 1)) |
ac023d77 JL |
377 | if os.WIFEXITED(rc) and os.WEXITSTATUS(rc) != 0: |
378 | bb.msg.fatal(bb.msg.domain.Build, ("Cannot proceed with manual patch resolution - '%s' not found. " \ | |
1de1ac2e | 379 | + "Check TERMCMDRUN variable.") % bb.data.getVar('TERMCMDRUN', self.patchset.d, 1)) |
ac023d77 JL |
380 | |
381 | # Construct a new PatchSet after the user's changes, compare the | |
382 | # sets, checking patches for modifications, and doing a remote | |
383 | # refresh on each. | |
384 | oldpatchset = self.patchset | |
385 | self.patchset = oldpatchset.__class__(self.patchset.dir, self.patchset.d) | |
386 | ||
387 | for patch in self.patchset.patches: | |
388 | oldpatch = None | |
389 | for opatch in oldpatchset.patches: | |
390 | if opatch["quiltfile"] == patch["quiltfile"]: | |
391 | oldpatch = opatch | |
392 | ||
393 | if oldpatch: | |
394 | patch["remote"] = oldpatch["remote"] | |
395 | if patch["quiltfile"] == oldpatch["quiltfile"]: | |
396 | if patch["quiltfilemd5"] != oldpatch["quiltfilemd5"]: | |
397 | bb.note("Patch %s has changed, updating remote url %s" % (os.path.basename(patch["quiltfile"]), patch["remote"])) | |
398 | # user change? remote refresh | |
399 | self.patchset.Refresh(remote=True, patch=self.patchset.patches.index(patch)) | |
400 | else: | |
401 | # User did not fix the problem. Abort. | |
402 | raise PatchError("Patch application failed, and user did not fix and refresh the patch.") | |
403 | except Exception: | |
404 | os.chdir(olddir) | |
405 | raise | |
406 | os.chdir(olddir) |