tools/update-packaging/make_incremental_updates.py

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rwxr-xr-x

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial