# ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is Mozilla build system. # # The Initial Developer of the Original Code is # Mozilla Foundation. # Portions created by the Initial Developer are Copyright (C) 2007 # the Initial Developer. All Rights Reserved. # # Contributor(s): # John Wolfe # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** ################################################################ # # make-wince-cab.py --- Given a directory, walk it and make an # installer based upon the contents of that directory # # Usage: python make-wince-inf.py CABWIZ_PATH SOURCE_DIR PROGRAM_NAME CAB_FINAL_NAME # # Walk through the relative directory SOURCE_DIR, parsing filenames # Checks for duplicate filenames and renames where needed # Then builds a WinMobile INF file based upon results # # Each directory in SOURCE_DIR may have a file named # 'install-exceptions', which lists files in SOURCE_DIR that # need not be placed into an installation CAB file. The # 'install-exceptions' file itself is always ignored. # # Blank lines and '#' comments in the 'install-exceptions' file are # ignored. # # EXAMPLE OF COMMAND LINE: # python make_wince_inf.py /c/Program\ Files/Microsoft\ Visual\ Studio\ 9.0/SmartDevices/SDK/SDKTools/cabwiz.exe dist/fennec Fennec fennec-0.11.en-US.wince-arm.cab # # ARGS: # cabiz_path - Path to CABWIZ.EXE executable inside Visual Studio # # source_dir - sub-directory which contains the target program # NOTE: It is assumed that the application name is SOURCE_DIR.exe # EXAMPLE: source_dir=fennec, there should be a fennec/fennec.exe application # # program_name - Name of the program to place inside the INF file # # cab_final_name - actual final name for the produced CAB file # # NOTE: In our example, "fennec" is the directory [source_name] # "fennec.exe" is the application [$(source_name).exe], and # "Fennec" is the shortcut name [program_name] ################################################################ import sys import os from os.path import join from subprocess import call, STDOUT import fnmatch import string import shutil class FileEntry: def __init__(self, dirpath, dircount, filename, filecount, actual_filename): self.dirpath = dirpath self.dircount = dircount self.filename = filename self.filecount = filecount self.actual_filename = actual_filename class DirEntry: def __init__(self, dirpath, dircount, filecount): self.dirpath = dirpath self.dircount = dircount self.filecount = filecount # Ignore detritus left lying around by editing tools. ignored_patterns = ['*~', '.#*', '#*#', '*.orig', '*.rej'] file_entry_count = 0 file_entries = [] fcount = 0 fnames = {} directories = {} files_in_directory = [] # Return the contents of FILENAME, a 'install-exceptions' file, as # a dictionary whose keys are exactly the list of filenames, along # with the basename of FILENAME itself. If FILENAME does not exist, # return the empty dictionary. def read_exceptions(filename): exceptions = set() if os.path.exists(filename): f = file(filename) for line in f: line = line.strip() if line and line[0] != '#': exceptions.add(line) exceptions.add( os.path.basename(filename) ) f.close() return exceptions # Sorts two items based first upon directory count (index of # directory in list of directories), then upon filename. A way of # getting a list sorted into directories, and then alphabetically by # filename within each directory. def sort_dircount_then_filename(item1, item2): # First Compare dirpaths if item1.dircount != item2.dircount: return cmp(item1.dircount, item2.dircount) # Next compare filenames next_result = cmp(item1.filename, item2.filename) if next_result != 0: return next_result # Lastly, compare filecounts return cmp(item1.filecount, item2.filecount) def handle_duplicate_filename(dirpath, filename, filecount): if filecount == 1: return filename file_parts = os.path.splitext(filename) new_filename = "%s_%d%s" % (file_parts[0], filecount, file_parts[1]) old_relative_filename = join(dirpath, filename) new_relative_filename = join(dirpath, new_filename) shutil.copyfile(old_relative_filename, new_relative_filename) return new_filename def add_new_entry(dirpath, dircount, filename, filecount): global file_entries global file_entry_count actual_filename = handle_duplicate_filename(dirpath, filename, filecount) file_entries.append( FileEntry(dirpath, dircount, filename, filecount, actual_filename) ) file_entry_count += 1 def walk_tree(start_dir, ignore): global fcount global fnames global directories global files_in_directory dircount = 0; files_in_directory = [0] for (dirpath, dirnames, filenames) in os.walk(start_dir): exceptions = read_exceptions(join(dirpath, 'install-exceptions')) dircount += 1 directories[dirpath] = DirEntry(dirpath, dircount, len(filenames)) if len(filenames) < 1: print "WARNING: No files in directory [%s]" % dirpath for filename in filenames: if len(exceptions) > 0 and filename in exceptions: continue if any(fnmatch.fnmatch(filename, p) for p in ignore): continue filecount = 1 if filename in fnames: filecount = fnames[filename] + 1 fnames[filename] = filecount add_new_entry(dirpath, dircount, filename, filecount) def output_inf_file_header(f, program_name): f.write("""; Duplicated Filenames ==> One of the two files ; needs renaming before packaging up the CAB ; ; Technique: Rename the second directory's file to XXXX_1 prior to starting the CABWIZ, ; then take care of the name in the File.xxxxx section [Version] Signature = "$Windows NT$" ; required as-is Provider = "Mozilla" ; maximum of 30 characters, full app name will be \" \" CESignature = "$Windows CE$" ; required as-is [CEStrings] AppName = "%s" ; maximum of 40 characters, full app name will be \" \"\n""" % program_name) f.write("InstallDir = %CE1%\\%AppName% ; Program Files\Fennec\n\n") def output_sourcedisksnames_section(f, dirs): f.write("[SourceDisksNames] ; directory that holds the application's files\n") for dir in dirs: f.write("""%d = , "%s",,%s\n""" % (directories[dir].dircount, dir, dir)) f.write(" \n") def output_sourcedisksfiles_section(f): f.write("[SourceDisksFiles] ; list of files to be included in .cab\n") for entry in sorted(file_entries, sort_dircount_then_filename): f.write("%s=%d\n" % (entry.actual_filename, entry.dircount)) f.write("\n") def output_defaultinstall_section(f, dirs): copyfileslist = "" copyfilesrawlist=[] for dir in dirs: if directories[dir].filecount < 1: continue copyfilesrawlist.append( "Files.%s" % '.'.join( dir.split('\\') ) ) prefix = ", " copyfileslist = ','.join(copyfilesrawlist) f.write("""[DefaultInstall] ; operations to be completed during install CopyFiles = %s AddReg = RegData CEShortcuts = Shortcuts \n""" % copyfileslist) def output_destinationdirs_section(f, dirs): f.write("[DestinationDirs] ; default destination directories for each operation section\n") for dir in dirs: dir_split = dir.split('\\') mod_dir_string = '.'.join(dir_split) if len(dir_split) > 1: dir_minus_top_level = '\\'.join(dir_split[1:]) else: dir_minus_top_level = "" if dir_minus_top_level: dir_minus_top_level = "\\%s" % dir_minus_top_level if directories[dir].filecount < 1: f.write(";Files.%s = 0, %%InstallDir%%%s ; NO FILES IN THIS DIRECTORY\n" % (mod_dir_string, dir_minus_top_level)) else: f.write("Files.%s = 0, %%InstallDir%%%s\n" % (mod_dir_string, dir_minus_top_level)) f.write("Shortcuts = 0, %CE11% ; \Windows\Start Menu\Programs\n\n") def output_directory_sections(f, dirs): for dir in dirs: if directories[dir].filecount < 1: f.write(";[Files.%s]\n;===NO FILES===\n" % '.'.join( dir.split('\\') )) else: f.write("[Files.%s]\n" % '.'.join( dir.split('\\') )) for entry in file_entries: if entry.dirpath == dir: f.write("\"%s\",%s\n" % (entry.filename, entry.actual_filename)) f.write("\n") def output_registry_section(f): f.write("""[RegData] ; registry key list HKCU,Software\%AppName%,MajorVersion,0x00010001,1 HKCU,Software\%AppName%,MinorVersion,0x00010001,0 \n""") def output_shortcuts_section(f, app_name): f.write("[Shortcuts] ; Shortcut created in destination dir, %CE14%\n") f.write("%%AppName%%,0,%s\n" % app_name) def output_inf_file(program_name, app_name): global files_in_directory inf_name = "%s.inf" % program_name f = open(inf_name, 'w') output_inf_file_header(f, program_name) dirs = sorted(directories) output_sourcedisksnames_section(f, dirs) output_sourcedisksfiles_section(f) output_defaultinstall_section(f, dirs) output_destinationdirs_section(f, dirs) output_directory_sections(f, dirs) output_registry_section(f) output_shortcuts_section(f, app_name) f.close() def make_cab_file(cabwiz_path, program_name, cab_final_name): make_cab_command = "\"%s\" %s.inf /compress" % (cabwiz_path, program_name) print "INFORMATION: Executing command to make %s CAB file (only works on BASH)" % program_name print " [%s]" % make_cab_command sys.stdout.flush() success = call([cabwiz_path, "%s.inf" % program_name, "/compress"], stdout=open("NUL:","w"), stderr=STDOUT) if not os.path.isfile("%s.CAB" % program_name): print """*************************************************************************** ERROR: CAB FILE NOT CREATED. You can try running the command by hand: %s" % make_cab_comman ---- NOTE: If you see an error like this: Error: File XXXXXXXXXX.inf contains DirIDs, which are not supported -- this may mean that your PYTHON is outputting Windows files WITHOUT CR-LF line endings. Please verify that your INF file has CR-LF line endings. ***************************************************************************""" return print "INFORMATION: Executing command to move %s.CAB to %s" % (program_name, cab_final_name) sys.stdout.flush() shutil.move("%s.CAB" % program_name, cab_final_name) def purge_copied_files(): for entry in file_entries: if entry.filename != entry.actual_filename: new_relative_filename = join(entry.dirpath, entry.actual_filename) os.remove(new_relative_filename) def main(): if len(sys.argv) != 5: print >> sys.stderr, "Usage: %s CABWIZ_PATH SOURCE_DIR PROGRAM_NAME CAB_FINAL_NAME" % sys.argv[0] print >> sys.stderr, "Example: %s /c/Program\ Files/Microsoft\ Visual\ Studio\ 9.0/ fennec Fennec fennec-0.11.en-US.wince-arm.cab" % sys.argv[0] sys.exit(1) cabwiz_path = sys.argv[1] source_dir = sys.argv[2] program_name = sys.argv[3] app_name = "%s.exe" % source_dir cab_final_name = sys.argv[4] if not os.path.isfile(cabwiz_path): print """*************************************************************************** ERROR: CABWIZ_PATH is not a valid file! Perhaps your VSINSTALLDIR is not properly set up? EXITING... ***************************************************************************""" sys.exit(2) walk_tree(source_dir, ignored_patterns) sys.stdout.flush() output_inf_file(program_name, app_name) sys.stdout.flush() make_cab_file(cabwiz_path, program_name, cab_final_name) purge_copied_files() # run main if run directly if __name__ == "__main__": main()