|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 import os |
|
6 import shutil |
|
7 import sha |
|
8 from os.path import join, getsize |
|
9 from stat import * |
|
10 import re |
|
11 import sys |
|
12 import getopt |
|
13 import time |
|
14 import datetime |
|
15 import bz2 |
|
16 import string |
|
17 import tempfile |
|
18 |
|
19 class PatchInfo: |
|
20 """ Represents the meta-data associated with a patch |
|
21 work_dir = working dir where files are stored for this patch |
|
22 archive_files = list of files to include in this patch |
|
23 manifestv2 = set of manifest version 2 patch instructions |
|
24 manifestv3 = set of manifest version 3 patch instructions |
|
25 file_exclusion_list = |
|
26 files to exclude from this patch. names without slashes will be |
|
27 excluded anywhere in the directory hiearchy. names with slashes |
|
28 will only be excluded at that exact path |
|
29 """ |
|
30 def __init__(self, work_dir, file_exclusion_list, path_exclusion_list): |
|
31 self.work_dir=work_dir |
|
32 self.archive_files=[] |
|
33 self.manifestv2=[] |
|
34 self.manifestv3=[] |
|
35 self.file_exclusion_list=file_exclusion_list |
|
36 self.path_exclusion_list=path_exclusion_list |
|
37 |
|
38 def append_add_instruction(self, filename): |
|
39 """ Appends an add instruction for this patch. |
|
40 if filename starts with distribution/extensions/.*/ this will add an |
|
41 add-if instruction that will add the file if the parent directory |
|
42 of the file exists. This was ported from |
|
43 mozilla/tools/update-packaging/common.sh's make_add_instruction. |
|
44 """ |
|
45 m = re.match("((?:|.*/)distribution/extensions/.*)/", filename) |
|
46 if m: |
|
47 # Directory immediately following extensions is used for the test |
|
48 testdir = m.group(1) |
|
49 print ' add-if "'+testdir+'" "'+filename+'"' |
|
50 self.manifestv2.append('add-if "'+testdir+'" "'+filename+'"') |
|
51 self.manifestv3.append('add-if "'+testdir+'" "'+filename+'"') |
|
52 else: |
|
53 print ' add "'+filename+'"' |
|
54 self.manifestv2.append('add "'+filename+'"') |
|
55 self.manifestv3.append('add "'+filename+'"') |
|
56 |
|
57 def append_add_if_not_instruction(self, filename): |
|
58 """ Appends an add-if-not instruction to the version 3 manifest for this patch. |
|
59 This was ported from mozilla/tools/update-packaging/common.sh's |
|
60 make_add_if_not_instruction. |
|
61 """ |
|
62 print ' add-if-not "'+filename+'" "'+filename+'"' |
|
63 self.manifestv3.append('add-if-not "'+filename+'" "'+filename+'"') |
|
64 |
|
65 def append_patch_instruction(self, filename, patchname): |
|
66 """ Appends a patch instruction for this patch. |
|
67 |
|
68 filename = file to patch |
|
69 patchname = patchfile to apply to file |
|
70 |
|
71 if filename starts with distribution/extensions/.*/ this will add a |
|
72 patch-if instruction that will patch the file if the parent |
|
73 directory of the file exists. This was ported from |
|
74 mozilla/tools/update-packaging/common.sh's make_patch_instruction. |
|
75 """ |
|
76 m = re.match("((?:|.*/)distribution/extensions/.*)/", filename) |
|
77 if m: |
|
78 testdir = m.group(1) |
|
79 print ' patch-if "'+testdir+'" "'+patchname+'" "'+filename+'"' |
|
80 self.manifestv2.append('patch-if "'+testdir+'" "'+patchname+'" "'+filename+'"') |
|
81 self.manifestv3.append('patch-if "'+testdir+'" "'+patchname+'" "'+filename+'"') |
|
82 else: |
|
83 print ' patch "'+patchname+'" "'+filename+'"' |
|
84 self.manifestv2.append('patch "'+patchname+'" "'+filename+'"') |
|
85 self.manifestv3.append('patch "'+patchname+'" "'+filename+'"') |
|
86 |
|
87 def append_remove_instruction(self, filename): |
|
88 """ Appends an remove instruction for this patch. |
|
89 This was ported from |
|
90 mozilla/tools/update-packaging/common.sh/make_remove_instruction |
|
91 """ |
|
92 if filename.endswith("/"): |
|
93 print ' rmdir "'+filename+'"' |
|
94 self.manifestv2.append('rmdir "'+filename+'"') |
|
95 self.manifestv3.append('rmdir "'+filename+'"') |
|
96 elif filename.endswith("/*"): |
|
97 filename = filename[:-1] |
|
98 print ' rmrfdir "'+filename+'"' |
|
99 self.manifestv2.append('rmrfdir "'+filename+'"') |
|
100 self.manifestv3.append('rmrfdir "'+filename+'"') |
|
101 else: |
|
102 print ' remove "'+filename+'"' |
|
103 self.manifestv2.append('remove "'+filename+'"') |
|
104 self.manifestv3.append('remove "'+filename+'"') |
|
105 |
|
106 def create_manifest_files(self): |
|
107 """ Create the v2 manifest file in the root of the work_dir """ |
|
108 manifest_file_path = os.path.join(self.work_dir,"updatev2.manifest") |
|
109 manifest_file = open(manifest_file_path, "wb") |
|
110 manifest_file.writelines("type \"partial\"\n") |
|
111 manifest_file.writelines(string.join(self.manifestv2, '\n')) |
|
112 manifest_file.writelines("\n") |
|
113 manifest_file.close() |
|
114 |
|
115 bzip_file(manifest_file_path) |
|
116 self.archive_files.append('"updatev2.manifest"') |
|
117 |
|
118 """ Create the v3 manifest file in the root of the work_dir """ |
|
119 manifest_file_path = os.path.join(self.work_dir,"updatev3.manifest") |
|
120 manifest_file = open(manifest_file_path, "wb") |
|
121 manifest_file.writelines("type \"partial\"\n") |
|
122 manifest_file.writelines(string.join(self.manifestv3, '\n')) |
|
123 manifest_file.writelines("\n") |
|
124 manifest_file.close() |
|
125 |
|
126 bzip_file(manifest_file_path) |
|
127 self.archive_files.append('"updatev3.manifest"') |
|
128 |
|
129 def build_marfile_entry_hash(self, root_path): |
|
130 """ Iterates through the root_path, creating a MarFileEntry for each file |
|
131 and directory in that path. Excludes any filenames in the file_exclusion_list |
|
132 """ |
|
133 mar_entry_hash = {} |
|
134 filename_set = set() |
|
135 dirname_set = set() |
|
136 for root, dirs, files in os.walk(root_path): |
|
137 for name in files: |
|
138 # filename is the relative path from root directory |
|
139 partial_path = root[len(root_path)+1:] |
|
140 if name not in self.file_exclusion_list: |
|
141 filename = os.path.join(partial_path, name) |
|
142 if "/"+filename not in self.path_exclusion_list: |
|
143 mar_entry_hash[filename]=MarFileEntry(root_path, filename) |
|
144 filename_set.add(filename) |
|
145 |
|
146 for name in dirs: |
|
147 # dirname is the relative path from root directory |
|
148 partial_path = root[len(root_path)+1:] |
|
149 if name not in self.file_exclusion_list: |
|
150 dirname = os.path.join(partial_path, name) |
|
151 if "/"+dirname not in self.path_exclusion_list: |
|
152 dirname = dirname+"/" |
|
153 mar_entry_hash[dirname]=MarFileEntry(root_path, dirname) |
|
154 dirname_set.add(dirname) |
|
155 |
|
156 return mar_entry_hash, filename_set, dirname_set |
|
157 |
|
158 |
|
159 class MarFileEntry: |
|
160 """Represents a file inside a Mozilla Archive Format (MAR) |
|
161 abs_path = abspath to the the file |
|
162 name = relative path within the mar. e.g. |
|
163 foo.mar/dir/bar.txt extracted into /tmp/foo: |
|
164 abs_path=/tmp/foo/dir/bar.txt |
|
165 name = dir/bar.txt |
|
166 """ |
|
167 def __init__(self, root, name): |
|
168 """root = path the the top of the mar |
|
169 name = relative path within the mar""" |
|
170 self.name=name.replace("\\", "/") |
|
171 self.abs_path=os.path.join(root,name) |
|
172 self.sha_cache=None |
|
173 |
|
174 def __str__(self): |
|
175 return 'Name: %s FullPath: %s' %(self.name,self.abs_path) |
|
176 |
|
177 def calc_file_sha_digest(self, filename): |
|
178 """ Returns sha digest of given filename""" |
|
179 file_content = open(filename, 'r').read() |
|
180 return sha.new(file_content).digest() |
|
181 |
|
182 def sha(self): |
|
183 """ Returns sha digest of file repreesnted by this _marfile_entry""" |
|
184 if not self.sha_cache: |
|
185 self.sha_cache=self.calc_file_sha_digest(self.abs_path) |
|
186 return self.sha_cache |
|
187 |
|
188 def exec_shell_cmd(cmd): |
|
189 """Execs shell cmd and raises an exception if the cmd fails""" |
|
190 if (os.system(cmd)): |
|
191 raise Exception, "cmd failed "+cmd |
|
192 |
|
193 |
|
194 def copy_file(src_file_abs_path, dst_file_abs_path): |
|
195 """ Copies src to dst creating any parent dirs required in dst first """ |
|
196 dst_file_dir=os.path.dirname(dst_file_abs_path) |
|
197 if not os.path.exists(dst_file_dir): |
|
198 os.makedirs(dst_file_dir) |
|
199 # Copy the file over |
|
200 shutil.copy2(src_file_abs_path, dst_file_abs_path) |
|
201 |
|
202 def bzip_file(filename): |
|
203 """ Bzip's the file in place. The original file is replaced with a bzip'd version of itself |
|
204 assumes the path is absolute""" |
|
205 exec_shell_cmd('bzip2 -z9 "' + filename+'"') |
|
206 os.rename(filename+".bz2",filename) |
|
207 |
|
208 def bunzip_file(filename): |
|
209 """ Bzip's the file in palce. The original file is replaced with a bunzip'd version of itself. |
|
210 doesn't matter if the filename ends in .bz2 or not""" |
|
211 if not filename.endswith(".bz2"): |
|
212 os.rename(filename, filename+".bz2") |
|
213 filename=filename+".bz2" |
|
214 exec_shell_cmd('bzip2 -d "' + filename+'"') |
|
215 |
|
216 |
|
217 def extract_mar(filename, work_dir): |
|
218 """ Extracts the marfile intot he work_dir |
|
219 assumes work_dir already exists otherwise will throw osError""" |
|
220 print "Extracting "+filename+" to "+work_dir |
|
221 saved_path = os.getcwd() |
|
222 try: |
|
223 os.chdir(work_dir) |
|
224 exec_shell_cmd("mar -x "+filename) |
|
225 finally: |
|
226 os.chdir(saved_path) |
|
227 |
|
228 def create_partial_patch_for_file(from_marfile_entry, to_marfile_entry, shas, patch_info): |
|
229 """ Creates the partial patch file and manifest entry for the pair of files passed in |
|
230 """ |
|
231 if not (from_marfile_entry.sha(),to_marfile_entry.sha()) in shas: |
|
232 print 'diffing "'+from_marfile_entry.name+'\"' |
|
233 #bunzip to/from |
|
234 bunzip_file(from_marfile_entry.abs_path) |
|
235 bunzip_file(to_marfile_entry.abs_path) |
|
236 |
|
237 # The patch file will be created in the working directory with the |
|
238 # name of the file in the mar + .patch |
|
239 patch_file_abs_path = os.path.join(patch_info.work_dir,from_marfile_entry.name+".patch") |
|
240 patch_file_dir=os.path.dirname(patch_file_abs_path) |
|
241 if not os.path.exists(patch_file_dir): |
|
242 os.makedirs(patch_file_dir) |
|
243 |
|
244 # Create bzip'd patch file |
|
245 exec_shell_cmd("mbsdiff "+from_marfile_entry.abs_path+" "+to_marfile_entry.abs_path+" "+patch_file_abs_path) |
|
246 bzip_file(patch_file_abs_path) |
|
247 |
|
248 # Create bzip's full file |
|
249 full_file_abs_path = os.path.join(patch_info.work_dir, to_marfile_entry.name) |
|
250 shutil.copy2(to_marfile_entry.abs_path, full_file_abs_path) |
|
251 bzip_file(full_file_abs_path) |
|
252 |
|
253 if os.path.getsize(patch_file_abs_path) < os.path.getsize(full_file_abs_path): |
|
254 # Patch is smaller than file. Remove the file and add patch to manifest |
|
255 os.remove(full_file_abs_path) |
|
256 file_in_manifest_name = from_marfile_entry.name+".patch" |
|
257 file_in_manifest_abspath = patch_file_abs_path |
|
258 patch_info.append_patch_instruction(to_marfile_entry.name, file_in_manifest_name) |
|
259 else: |
|
260 # File is smaller than patch. Remove the patch and add file to manifest |
|
261 os.remove(patch_file_abs_path) |
|
262 file_in_manifest_name = from_marfile_entry.name |
|
263 file_in_manifest_abspath = full_file_abs_path |
|
264 patch_info.append_add_instruction(file_in_manifest_name) |
|
265 |
|
266 shas[from_marfile_entry.sha(),to_marfile_entry.sha()] = (file_in_manifest_name,file_in_manifest_abspath) |
|
267 patch_info.archive_files.append('"'+file_in_manifest_name+'"') |
|
268 else: |
|
269 filename, src_file_abs_path = shas[from_marfile_entry.sha(),to_marfile_entry.sha()] |
|
270 # We've already calculated the patch for this pair of files. |
|
271 if (filename.endswith(".patch")): |
|
272 # print "skipping diff: "+from_marfile_entry.name |
|
273 # Patch was smaller than file - add patch instruction to manifest |
|
274 file_in_manifest_name = to_marfile_entry.name+'.patch'; |
|
275 patch_info.append_patch_instruction(to_marfile_entry.name, file_in_manifest_name) |
|
276 else: |
|
277 # File was smaller than file - add file to manifest |
|
278 file_in_manifest_name = to_marfile_entry.name |
|
279 patch_info.append_add_instruction(file_in_manifest_name) |
|
280 # Copy the pre-calculated file into our new patch work aread |
|
281 copy_file(src_file_abs_path, os.path.join(patch_info.work_dir, file_in_manifest_name)) |
|
282 patch_info.archive_files.append('"'+file_in_manifest_name+'"') |
|
283 |
|
284 def create_add_patch_for_file(to_marfile_entry, patch_info): |
|
285 """ Copy the file to the working dir, add the add instruction, and add it to the list of archive files """ |
|
286 copy_file(to_marfile_entry.abs_path, os.path.join(patch_info.work_dir, to_marfile_entry.name)) |
|
287 patch_info.append_add_instruction(to_marfile_entry.name) |
|
288 patch_info.archive_files.append('"'+to_marfile_entry.name+'"') |
|
289 |
|
290 def create_add_if_not_patch_for_file(to_marfile_entry, patch_info): |
|
291 """ Copy the file to the working dir, add the add-if-not instruction, and add it to the list of archive files """ |
|
292 copy_file(to_marfile_entry.abs_path, os.path.join(patch_info.work_dir, to_marfile_entry.name)) |
|
293 patch_info.append_add_if_not_instruction(to_marfile_entry.name) |
|
294 patch_info.archive_files.append('"'+to_marfile_entry.name+'"') |
|
295 |
|
296 def process_explicit_remove_files(dir_path, patch_info): |
|
297 """ Looks for a 'removed-files' file in the dir_path. If the removed-files does not exist |
|
298 this will throw. If found adds the removed-files |
|
299 found in that file to the patch_info""" |
|
300 |
|
301 # Windows and linux have this file at the root of the dir |
|
302 list_file_path = os.path.join(dir_path, "removed-files") |
|
303 prefix="" |
|
304 if not os.path.exists(list_file_path): |
|
305 # On Mac removed-files contains relative paths from Contents/MacOS/ |
|
306 prefix= "Contents/MacOS" |
|
307 list_file_path = os.path.join(dir_path, prefix+"/removed-files") |
|
308 |
|
309 if (os.path.exists(list_file_path)): |
|
310 list_file = bz2.BZ2File(list_file_path,"r") # throws if doesn't exist |
|
311 |
|
312 lines = [] |
|
313 for line in list_file: |
|
314 lines.append(line.strip()) |
|
315 |
|
316 lines.sort(reverse=True) |
|
317 for line in lines: |
|
318 # Exclude any blank and comment lines. |
|
319 if line and not line.startswith("#"): |
|
320 if prefix != "": |
|
321 if line.startswith("../"): |
|
322 line = line.replace("../../", "") |
|
323 line = line.replace("../", "Contents/") |
|
324 else: |
|
325 line = os.path.join(prefix,line) |
|
326 # Python on windows uses \ for path separators and the update |
|
327 # manifests expects / for path separators on all platforms. |
|
328 line = line.replace("\\", "/") |
|
329 patch_info.append_remove_instruction(line) |
|
330 |
|
331 def create_partial_patch(from_dir_path, to_dir_path, patch_filename, shas, patch_info, forced_updates, add_if_not_list): |
|
332 """ Builds a partial patch by comparing the files in from_dir_path to those of to_dir_path""" |
|
333 # Cannocolize the paths for safey |
|
334 from_dir_path = os.path.abspath(from_dir_path) |
|
335 to_dir_path = os.path.abspath(to_dir_path) |
|
336 # Create a hashtable of the from and to directories |
|
337 from_dir_hash,from_file_set,from_dir_set = patch_info.build_marfile_entry_hash(from_dir_path) |
|
338 to_dir_hash,to_file_set,to_dir_set = patch_info.build_marfile_entry_hash(to_dir_path) |
|
339 # Require that the precomplete file is included in the complete update |
|
340 if "precomplete" not in to_file_set: |
|
341 raise Exception, "missing precomplete file in: "+to_dir_path |
|
342 # Create a list of the forced updates |
|
343 forced_list = forced_updates.strip().split('|') |
|
344 forced_list.append("precomplete") |
|
345 |
|
346 # Files which exist in both sets need to be patched |
|
347 patch_filenames = list(from_file_set.intersection(to_file_set)) |
|
348 patch_filenames.sort(reverse=True) |
|
349 for filename in patch_filenames: |
|
350 from_marfile_entry = from_dir_hash[filename] |
|
351 to_marfile_entry = to_dir_hash[filename] |
|
352 if os.path.basename(filename) in add_if_not_list: |
|
353 # This filename is in the add if not list, explicitly add-if-not |
|
354 create_add_if_not_patch_for_file(to_dir_hash[filename], patch_info) |
|
355 elif filename in forced_list: |
|
356 print 'Forcing "'+filename+'"' |
|
357 # This filename is in the forced list, explicitly add |
|
358 create_add_patch_for_file(to_dir_hash[filename], patch_info) |
|
359 else: |
|
360 if from_marfile_entry.sha() != to_marfile_entry.sha(): |
|
361 # Not the same - calculate a patch |
|
362 create_partial_patch_for_file(from_marfile_entry, to_marfile_entry, shas, patch_info) |
|
363 |
|
364 # files in to_dir not in from_dir need to added |
|
365 add_filenames = list(to_file_set - from_file_set) |
|
366 add_filenames.sort(reverse=True) |
|
367 for filename in add_filenames: |
|
368 if os.path.basename(filename) in add_if_not_list: |
|
369 create_add_if_not_patch_for_file(to_dir_hash[filename], patch_info) |
|
370 else: |
|
371 create_add_patch_for_file(to_dir_hash[filename], patch_info) |
|
372 |
|
373 # files in from_dir not in to_dir need to be removed |
|
374 remove_filenames = list(from_file_set - to_file_set) |
|
375 remove_filenames.sort(reverse=True) |
|
376 for filename in remove_filenames: |
|
377 patch_info.append_remove_instruction(from_dir_hash[filename].name) |
|
378 |
|
379 process_explicit_remove_files(to_dir_path, patch_info) |
|
380 |
|
381 # directories in from_dir not in to_dir need to be removed |
|
382 remove_dirnames = list(from_dir_set - to_dir_set) |
|
383 remove_dirnames.sort(reverse=True) |
|
384 for dirname in remove_dirnames: |
|
385 patch_info.append_remove_instruction(from_dir_hash[dirname].name) |
|
386 |
|
387 # Construct the Manifest files |
|
388 patch_info.create_manifest_files() |
|
389 |
|
390 # And construct the mar |
|
391 mar_cmd = 'mar -C '+patch_info.work_dir+' -c output.mar '+string.join(patch_info.archive_files, ' ') |
|
392 exec_shell_cmd(mar_cmd) |
|
393 |
|
394 # Copy mar to final destination |
|
395 patch_file_dir = os.path.split(patch_filename)[0] |
|
396 if not os.path.exists(patch_file_dir): |
|
397 os.makedirs(patch_file_dir) |
|
398 shutil.copy2(os.path.join(patch_info.work_dir,"output.mar"), patch_filename) |
|
399 return patch_filename |
|
400 |
|
401 def usage(): |
|
402 print "-h for help" |
|
403 print "-f for patchlist_file" |
|
404 |
|
405 def get_buildid(work_dir, platform): |
|
406 """ extracts buildid from MAR |
|
407 TODO: this should handle 1.8 branch too |
|
408 """ |
|
409 if platform == 'mac': |
|
410 ini = '%s/Contents/MacOS/application.ini' % work_dir |
|
411 else: |
|
412 ini = '%s/application.ini' % work_dir |
|
413 if not os.path.exists(ini): |
|
414 print 'WARNING: application.ini not found, cannot find build ID' |
|
415 return '' |
|
416 file = bz2.BZ2File(ini) |
|
417 for line in file: |
|
418 if line.find('BuildID') == 0: |
|
419 return line.strip().split('=')[1] |
|
420 print 'WARNING: cannot find build ID in application.ini' |
|
421 return '' |
|
422 |
|
423 def decode_filename(filepath): |
|
424 """ Breaks filename/dir structure into component parts based on regex |
|
425 for example: firefox-3.0b3pre.en-US.linux-i686.complete.mar |
|
426 Or linux-i686/en-US/firefox-3.0b3.complete.mar |
|
427 Returns dict with keys product, version, locale, platform, type |
|
428 """ |
|
429 try: |
|
430 m = re.search( |
|
431 '(?P<product>\w+)(-)(?P<version>\w+\.\w+(\.\w+){0,2})(\.)(?P<locale>.+?)(\.)(?P<platform>.+?)(\.)(?P<type>\w+)(.mar)', |
|
432 os.path.basename(filepath)) |
|
433 return m.groupdict() |
|
434 except Exception, exc: |
|
435 try: |
|
436 m = re.search( |
|
437 '(?P<platform>.+?)\/(?P<locale>.+?)\/(?P<product>\w+)-(?P<version>\w+\.\w+)\.(?P<type>\w+).mar', |
|
438 filepath) |
|
439 return m.groupdict() |
|
440 except: |
|
441 raise Exception("could not parse filepath %s: %s" % (filepath, exc)) |
|
442 |
|
443 def create_partial_patches(patches): |
|
444 """ Given the patches generates a set of partial patches""" |
|
445 shas = {} |
|
446 |
|
447 work_dir_root = None |
|
448 metadata = [] |
|
449 try: |
|
450 work_dir_root = tempfile.mkdtemp('-fastmode', 'tmp', os.getcwd()) |
|
451 print "Building patches using work dir: %s" % (work_dir_root) |
|
452 |
|
453 # Iterate through every patch set in the patch file |
|
454 patch_num = 1 |
|
455 for patch in patches: |
|
456 startTime = time.time() |
|
457 |
|
458 from_filename,to_filename,patch_filename,forced_updates = patch.split(",") |
|
459 from_filename,to_filename,patch_filename = os.path.abspath(from_filename),os.path.abspath(to_filename),os.path.abspath(patch_filename) |
|
460 |
|
461 # Each patch iteration uses its own work dir |
|
462 work_dir = os.path.join(work_dir_root,str(patch_num)) |
|
463 os.mkdir(work_dir) |
|
464 |
|
465 # Extract from mar into from dir |
|
466 work_dir_from = os.path.join(work_dir,"from"); |
|
467 os.mkdir(work_dir_from) |
|
468 extract_mar(from_filename,work_dir_from) |
|
469 from_decoded = decode_filename(from_filename) |
|
470 from_buildid = get_buildid(work_dir_from, from_decoded['platform']) |
|
471 from_shasum = sha.sha(open(from_filename).read()).hexdigest() |
|
472 from_size = str(os.path.getsize(to_filename)) |
|
473 |
|
474 # Extract to mar into to dir |
|
475 work_dir_to = os.path.join(work_dir,"to") |
|
476 os.mkdir(work_dir_to) |
|
477 extract_mar(to_filename, work_dir_to) |
|
478 to_decoded = decode_filename(from_filename) |
|
479 to_buildid = get_buildid(work_dir_to, to_decoded['platform']) |
|
480 to_shasum = sha.sha(open(to_filename).read()).hexdigest() |
|
481 to_size = str(os.path.getsize(to_filename)) |
|
482 |
|
483 mar_extract_time = time.time() |
|
484 |
|
485 partial_filename = create_partial_patch(work_dir_from, work_dir_to, patch_filename, shas, PatchInfo(work_dir, ['update.manifest','updatev2.manifest','updatev3.manifest','removed-files'],['/readme.txt']),forced_updates,['channel-prefs.js','update-settings.ini']) |
|
486 partial_buildid = to_buildid |
|
487 partial_shasum = sha.sha(open(partial_filename).read()).hexdigest() |
|
488 partial_size = str(os.path.getsize(partial_filename)) |
|
489 |
|
490 metadata.append({ |
|
491 'to_filename': os.path.basename(to_filename), |
|
492 'from_filename': os.path.basename(from_filename), |
|
493 'partial_filename': os.path.basename(partial_filename), |
|
494 'to_buildid':to_buildid, |
|
495 'from_buildid':from_buildid, |
|
496 'to_sha1sum':to_shasum, |
|
497 'from_sha1sum':from_shasum, |
|
498 'partial_sha1sum':partial_shasum, |
|
499 'to_size':to_size, |
|
500 'from_size':from_size, |
|
501 'partial_size':partial_size, |
|
502 'to_version':to_decoded['version'], |
|
503 'from_version':from_decoded['version'], |
|
504 'locale':from_decoded['locale'], |
|
505 'platform':from_decoded['platform'], |
|
506 }) |
|
507 print "done with patch %s/%s time (%.2fs/%.2fs/%.2fs) (mar/patch/total)" % (str(patch_num),str(len(patches)),mar_extract_time-startTime,time.time()-mar_extract_time,time.time()-startTime) |
|
508 patch_num += 1 |
|
509 return metadata |
|
510 finally: |
|
511 # If we fail or get a ctrl-c during run be sure to clean up temp dir |
|
512 if (work_dir_root and os.path.exists(work_dir_root)): |
|
513 shutil.rmtree(work_dir_root) |
|
514 |
|
515 def main(argv): |
|
516 patchlist_file = None |
|
517 try: |
|
518 opts, args = getopt.getopt(argv, "hf:", ["help", "patchlist_file="]) |
|
519 for opt, arg in opts: |
|
520 if opt in ("-h", "--help"): |
|
521 usage() |
|
522 sys.exit() |
|
523 elif opt in ("-f", "--patchlist_file"): |
|
524 patchlist_file = arg |
|
525 except getopt.GetoptError: |
|
526 usage() |
|
527 sys.exit(2) |
|
528 |
|
529 if not patchlist_file: |
|
530 usage() |
|
531 sys.exit(2) |
|
532 |
|
533 patches = [] |
|
534 f = open(patchlist_file, 'r') |
|
535 for line in f.readlines(): |
|
536 patches.append(line) |
|
537 f.close() |
|
538 create_partial_patches(patches) |
|
539 |
|
540 if __name__ == "__main__": |
|
541 main(sys.argv[1:]) |
|
542 |