4 from os.path
import join, getsize
16 """ Represents the meta-data associated with a patch
17 work_dir = working dir where files are stored for this patch
18 archive_files = list of files to include in this patch
19 manifestv1 = set of manifest version 1 patch instructions
20 manifestv2 = set of manifest version 2 patch instructions
22 files to exclude from this patch. names without slashes will be
23 excluded anywhere in the directory hiearchy. names with slashes
24 will only be excluded at that exact path
26 def __init__(self, work_dir, file_exclusion_list, path_exclusion_list):
35 """ Appends an add instruction for this patch.
36 if the filename starts with extensions/ adds an add-if instruction
37 to test the existence of the subdirectory. This was ported from
38 mozilla/tools/update-packaging/common.sh/make_add_instruction
40 if filename.startswith(
"extensions/"):
42 testdir =
"extensions/"+filename.split(
"/")[1]
43 self.manifestv1.append(
'add-if "'+testdir+
'" "'+filename+
'"')
44 self.manifestv2.append(
'add-if "'+testdir+
'" "'+filename+
'"')
45 elif filename.startswith(
"Contents/MacOS/extensions/"):
46 testdir =
"Contents/MacOS/extensions/"+filename.split(
"/")[3]
47 self.manifestv1.append(
'add-if "'+testdir+
'" "'+filename+
'"')
48 self.manifestv2.append(
'add-if "'+testdir+
'" "'+filename+
'"')
50 self.manifestv1.append(
'add "'+filename+
'"')
51 self.manifestv2.append(
'add "'+filename+
'"')
54 """ Appends an patch instruction for this patch.
56 filename = file to patch
57 patchname = patchfile to apply to file
59 if the filename starts with extensions/ adds a patch-if instruction
60 to test the existence of the subdirectory.
61 if the filename starts with searchplugins/ add a add-if instruction for the filename
63 mozilla/tools/update-packaging/common.sh/make_patch_instruction
65 if filename.startswith(
"extensions/"):
66 testdir =
"extensions/"+filename.split(
"/")[1]
67 self.manifestv1.append(
'patch-if "'+testdir+
'" "'+patchname+
'" "'+filename+
'"')
68 self.manifestv2.append(
'patch-if "'+testdir+
'" "'+patchname+
'" "'+filename+
'"')
69 elif filename.startswith(
"Contents/MacOS/extensions/"):
70 testdir =
"Contents/MacOS/extensions/"+filename.split(
"/")[3]
71 self.manifestv1.append(
'patch-if "'+testdir+
'" "'+patchname+
'" "'+filename+
'"')
72 self.manifestv2.append(
'patch-if "'+testdir+
'" "'+patchname+
'" "'+filename+
'"')
73 elif (filename.startswith(
"searchplugins/")
or
74 filename.startswith(
"Contents/MacOS/searchplugins/")):
75 self.manifestv1.append(
'patch-if "'+filename+
'" "'+patchname+
'" "'+filename+
'"')
76 self.manifestv2.append(
'patch-if "'+filename+
'" "'+patchname+
'" "'+filename+
'"')
78 self.manifestv1.append(
'patch "'+patchname+
'" "'+filename+
'"')
79 self.manifestv2.append(
'patch "'+patchname+
'" "'+filename+
'"')
82 """ Appends an remove instruction for this patch.
84 mozilla/tools/update-packaging/common.sh/make_remove_instruction
86 if filename.endswith(
"/"):
87 self.manifestv2.append(
'rmdir "'+filename+
'"')
88 elif filename.endswith(
"/*"):
89 filename = filename[:-1]
90 self.manifestv2.append(
'rmrfdir "'+filename+
'"')
92 self.manifestv1.append(
'remove "'+filename+
'"')
93 self.manifestv2.append(
'remove "'+filename+
'"')
96 """ Createst the v1 manifest file in the root of the work_dir """
97 manifest_file_path = os.path.join(self.
work_dir,
"update.manifest")
98 manifest_file = open(manifest_file_path,
"wb")
99 manifest_file.writelines(string.join(self.
manifestv1,
'\n'))
100 manifest_file.writelines(
"\n")
101 manifest_file.close()
104 self.archive_files.append(
'"update.manifest"')
106 """ Createst the v2 manifest file in the root of the work_dir """
107 manifest_file_path = os.path.join(self.
work_dir,
"updatev2.manifest")
108 manifest_file = open(manifest_file_path,
"wb")
109 manifest_file.writelines(
"type \"partial\"\n")
110 manifest_file.writelines(string.join(self.
manifestv2,
'\n'))
111 manifest_file.writelines(
"\n")
112 manifest_file.close()
115 self.archive_files.append(
'"updatev2.manifest"')
118 """ Iterates through the root_path, creating a MarFileEntry for each file
119 and directory in that path. Excludes any filenames in the file_exclusion_list
124 for root, dirs, files
in os.walk(root_path):
127 partial_path = root[len(root_path)+1:]
129 filename = os.path.join(partial_path, name)
131 mar_entry_hash[filename]=
MarFileEntry(root_path, filename)
132 filename_set.add(filename)
136 partial_path = root[len(root_path)+1:]
138 dirname = os.path.join(partial_path, name)
140 dirname = dirname+
"/"
141 mar_entry_hash[dirname]=
MarFileEntry(root_path, dirname)
142 dirname_set.add(dirname)
144 return mar_entry_hash, filename_set, dirname_set
148 """Represents a file inside a Mozilla Archive Format (MAR)
149 abs_path = abspath to the the file
150 name = relative path within the mar. e.g.
151 foo.mar/dir/bar.txt extracted into /tmp/foo:
152 abs_path=/tmp/foo/dir/bar.txt
156 """root = path the the top of the mar
157 name = relative path within the mar"""
158 self.
name=name.replace(
"\\",
"/")
163 return 'Name: %s FullPath: %s' %(self.
name,self.
abs_path)
166 """ Returns sha digest of given filename"""
167 file_content = open(filename,
'r').read()
168 return sha.new(file_content).digest()
171 """ Returns sha digest of file repreesnted by this _marfile_entry"""
177 """Execs shell cmd and raises an exception if the cmd fails"""
179 raise Exception,
"cmd failed "+cmd
183 """ Copies src to dst creating any parent dirs required in dst first """
184 dst_file_dir=os.path.dirname(dst_file_abs_path)
185 if not os.path.exists(dst_file_dir):
186 os.makedirs(dst_file_dir)
188 shutil.copy2(src_file_abs_path, dst_file_abs_path)
191 """ Bzip's the file in place. The original file is replaced with a bzip'd version of itself
192 assumes the path is absolute"""
194 os.rename(filename+
".bz2",filename)
197 """ Bzip's the file in palce. The original file is replaced with a bunzip'd version of itself.
198 doesn't matter if the filename ends in .bz2 or not"""
199 if not filename.endswith(
".bz2"):
200 os.rename(filename, filename+
".bz2")
201 filename=filename+
".bz2"
206 """ Extracts the marfile intot he work_dir
207 assumes work_dir already exists otherwise will throw osError"""
208 print "Extracting "+filename+
" to "+work_dir
209 saved_path = os.getcwd()
217 """ Creates the partial patch file and manifest entry for the pair of files passed in
219 if not (from_marfile_entry.sha(),to_marfile_entry.sha())
in shas:
220 print "diffing: " + from_marfile_entry.name
228 patch_file_abs_path = os.path.join(patch_info.work_dir,from_marfile_entry.name+
".patch")
229 patch_file_dir=os.path.dirname(patch_file_abs_path)
230 if not os.path.exists(patch_file_dir):
231 os.makedirs(patch_file_dir)
234 exec_shell_cmd(
"mbsdiff "+from_marfile_entry.abs_path+
" "+to_marfile_entry.abs_path+
" "+patch_file_abs_path)
238 full_file_abs_path = os.path.join(patch_info.work_dir, to_marfile_entry.name)
239 shutil.copy2(to_marfile_entry.abs_path, full_file_abs_path)
243 if os.path.getsize(patch_file_abs_path) < os.path.getsize(full_file_abs_path):
245 os.remove(full_file_abs_path)
246 file_in_manifest_name = from_marfile_entry.name+
".patch"
247 file_in_manifest_abspath = patch_file_abs_path
248 patch_info.append_patch_instruction(to_marfile_entry.name, file_in_manifest_name)
251 os.remove(patch_file_abs_path)
252 file_in_manifest_name = from_marfile_entry.name
253 file_in_manifest_abspath = full_file_abs_path
254 patch_info.append_add_instruction(file_in_manifest_name)
256 shas[from_marfile_entry.sha(),to_marfile_entry.sha()] = (file_in_manifest_name,file_in_manifest_abspath)
257 patch_info.archive_files.append(
'"'+file_in_manifest_name+
'"')
259 filename, src_file_abs_path = shas[from_marfile_entry.sha(),to_marfile_entry.sha()]
261 if (filename.endswith(
".patch")):
262 print "skipping diff: " + from_marfile_entry.name
264 file_in_manifest_name = to_marfile_entry.name+
'.patch';
265 patch_info.append_patch_instruction(to_marfile_entry.name, file_in_manifest_name)
268 file_in_manifest_name = to_marfile_entry.name
269 patch_info.append_add_instruction(file_in_manifest_name)
271 copy_file(src_file_abs_path, os.path.join(patch_info.work_dir, file_in_manifest_name))
272 patch_info.archive_files.append(
'"'+file_in_manifest_name+
'"')
275 """ Copy the file to the working dir, add the add instruction, and add it to the list of archive files """
276 print "Adding New File " + to_marfile_entry.name
277 copy_file(to_marfile_entry.abs_path, os.path.join(patch_info.work_dir, to_marfile_entry.name))
278 patch_info.append_add_instruction(to_marfile_entry.name)
279 patch_info.archive_files.append(
'"'+to_marfile_entry.name+
'"')
282 """ Looks for a 'removed-files' file in the dir_path. If the removed-files does not exist
283 this will throw. If found adds the removed-files
284 found in that file to the patch_info"""
287 list_file_path = os.path.join(dir_path,
"removed-files")
289 if not os.path.exists(list_file_path):
291 prefix=
"Contents/MacOS"
292 list_file_path = os.path.join(dir_path, prefix+
"/removed-files")
294 if (os.path.exists(list_file_path)):
295 list_file = bz2.BZ2File(list_file_path,
"r") # throws if doesn't exist
298 for line
in list_file:
299 lines.append(line.strip())
301 lines.sort(reverse=
True)
304 if line
and not line.startswith(
"#"):
306 if line.startswith(
"../"):
307 line = line.replace(
"../../",
"")
308 line = line.replace(
"../",
"Contents/")
310 line = os.path.join(prefix,line)
313 line = line.replace(
"\\",
"/")
314 patch_info.append_remove_instruction(line)
317 """ Builds a partial patch by comparing the files in from_dir_path to thoes of to_dir_path"""
319 from_dir_path = os.path.abspath(from_dir_path)
320 to_dir_path = os.path.abspath(to_dir_path)
322 from_dir_hash,from_file_set,from_dir_set = patch_info.build_marfile_entry_hash(from_dir_path)
323 to_dir_hash,to_file_set,to_dir_set = patch_info.build_marfile_entry_hash(to_dir_path)
325 if "precomplete" not in to_file_set:
326 raise Exception,
"missing precomplete file in: "+to_dir_path
328 forced_list = forced_updates.strip().split(
'|')
329 forced_list.append(
"precomplete")
332 patch_filenames = list(from_file_set.intersection(to_file_set))
333 patch_filenames.sort(reverse=
True)
334 for filename
in patch_filenames:
335 from_marfile_entry = from_dir_hash[filename]
336 to_marfile_entry = to_dir_hash[filename]
337 if filename
in forced_list:
338 print "Forcing "+ filename
342 if from_marfile_entry.sha() != to_marfile_entry.sha():
347 add_filenames = list(to_file_set - from_file_set)
348 add_filenames.sort(reverse=
True)
349 for filename
in add_filenames:
353 remove_filenames = list(from_file_set - to_file_set)
354 remove_filenames.sort(reverse=
True)
355 for filename
in remove_filenames:
356 patch_info.append_remove_instruction(from_dir_hash[filename].name)
361 remove_dirnames = list(from_dir_set - to_dir_set)
362 remove_dirnames.sort(reverse=
True)
363 for dirname
in remove_dirnames:
364 patch_info.append_remove_instruction(from_dir_hash[dirname].name)
367 patch_info.create_manifest_files()
370 mar_cmd =
'mar -C '+patch_info.work_dir+
' -c output.mar '+string.join(patch_info.archive_files,
' ')
374 patch_file_dir = os.path.split(patch_filename)[0]
375 if not os.path.exists(patch_file_dir):
376 os.makedirs(patch_file_dir)
377 shutil.copy2(os.path.join(patch_info.work_dir,
"output.mar"), patch_filename)
378 return patch_filename
382 print "-f for patchlist_file"
385 """ extracts buildid from MAR
386 TODO: this should handle 1.8 branch too
388 if platform ==
'mac':
389 ini =
'%s/Contents/MacOS/application.ini' % work_dir
391 ini =
'%s/application.ini' % work_dir
392 if not os.path.exists(ini):
393 print 'WARNING: application.ini not found, cannot find build ID'
395 file = bz2.BZ2File(ini)
397 if line.find(
'BuildID') == 0:
398 return line.strip().split(
'=')[1]
399 print 'WARNING: cannot find build ID in application.ini'
403 """ Breaks filename/dir structure into component parts based on regex
404 for example: firefox-3.0b3pre.en-US.linux-i686.complete.mar
405 Or linux-i686/en-US/firefox-3.0b3.complete.mar
406 Returns dict with keys product, version, locale, platform, type
410 '(?P<product>\w+)(-)(?P<version>\w+\.\w+(\.\w+){0,2})(\.)(?P<locale>.+?)(\.)(?P<platform>.+?)(\.)(?P<type>\w+)(.mar)',
411 os.path.basename(filepath))
413 except Exception, exc:
416 '(?P<platform>.+?)\/(?P<locale>.+?)\/(?P<product>\w+)-(?P<version>\w+\.\w+)\.(?P<type>\w+).mar',
420 raise Exception(
"could not parse filepath %s: %s" % (filepath, exc))
423 """ Given the patches generates a set of partial patches"""
429 work_dir_root = tempfile.mkdtemp(
'-fastmode',
'tmp', os.getcwd())
430 print "Building patches using work dir: %s" % (work_dir_root)
434 for patch
in patches:
435 startTime = time.time()
437 from_filename,to_filename,patch_filename,forced_updates = patch.split(
",")
438 from_filename,to_filename,patch_filename = os.path.abspath(from_filename),os.path.abspath(to_filename),os.path.abspath(patch_filename)
441 work_dir = os.path.join(work_dir_root,str(patch_num))
445 work_dir_from = os.path.join(work_dir,
"from");
446 os.mkdir(work_dir_from)
449 from_buildid =
get_buildid(work_dir_from, from_decoded[
'platform'])
450 from_shasum = sha.sha(open(from_filename).read()).hexdigest()
451 from_size = str(os.path.getsize(to_filename))
454 work_dir_to = os.path.join(work_dir,
"to")
455 os.mkdir(work_dir_to)
458 to_buildid =
get_buildid(work_dir_to, to_decoded[
'platform'])
459 to_shasum = sha.sha(open(to_filename).read()).hexdigest()
460 to_size = str(os.path.getsize(to_filename))
462 mar_extract_time = time.time()
464 partial_filename =
create_partial_patch(work_dir_from, work_dir_to, patch_filename, shas,
PatchInfo(work_dir, [
'channel-prefs.js',
'update.manifest',
'updatev2.manifest',
'removed-files'],[
'/readme.txt']),forced_updates)
465 partial_buildid = to_buildid
466 partial_shasum = sha.sha(open(partial_filename).read()).hexdigest()
467 partial_size = str(os.path.getsize(partial_filename))
470 'to_filename': os.path.basename(to_filename),
471 'from_filename': os.path.basename(from_filename),
472 'partial_filename': os.path.basename(partial_filename),
473 'to_buildid':to_buildid,
474 'from_buildid':from_buildid,
475 'to_sha1sum':to_shasum,
476 'from_sha1sum':from_shasum,
477 'partial_sha1sum':partial_shasum,
479 'from_size':from_size,
480 'partial_size':partial_size,
481 'to_version':to_decoded[
'version'],
482 'from_version':from_decoded[
'version'],
483 'locale':from_decoded[
'locale'],
484 'platform':from_decoded[
'platform'],
486 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)
491 if (work_dir_root
and os.path.exists(work_dir_root)):
492 shutil.rmtree(work_dir_root)
495 patchlist_file =
None
497 opts, args = getopt.getopt(argv,
"hf:", [
"help",
"patchlist_file="])
498 for opt, arg
in opts:
499 if opt
in (
"-h",
"--help"):
502 elif opt
in (
"-f",
"--patchlist_file"):
504 except getopt.GetoptError:
508 if not patchlist_file:
513 f = open(patchlist_file,
'r')
514 for line
in f.readlines():
519 if __name__ ==
"__main__":
def create_manifest_files
def append_add_instruction
def create_partial_patch_for_file
def create_add_patch_for_file
def process_explicit_remove_files
def append_remove_instruction
def build_marfile_entry_hash
def append_patch_instruction
def create_partial_patches