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