build/buildutils/svn2ccm_v3.py
changeset 21 2a9601315dfc
child 87 1627c337e51e
equal deleted inserted replaced
18:e8e63152f320 21:2a9601315dfc
       
     1 #
       
     2 # Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     3 # All rights reserved.
       
     4 # This component and the accompanying materials are made available
       
     5 # under the terms of "Eclipse Public License v1.0"
       
     6 # which accompanies this distribution, and is available
       
     7 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 #
       
     9 # Initial Contributors:
       
    10 # Nokia Corporation - initial contribution.
       
    11 #
       
    12 # Contributors:
       
    13 #
       
    14 # Description:
       
    15 #
       
    16 # Script for copying directory contents or SVN project
       
    17 # to Telelogic Synergy (CCM) project.
       
    18 #
       
    19 # The target directory specified in <ccm_path> parameter must
       
    20 # exist in ccm version control before this script is executed.
       
    21 #
       
    22 # If SVN URL and revision are specified the <svn_path>
       
    23 # directory must not exist.
       
    24 #
       
    25 # Script performs the following steps:
       
    26 # 1) If SVN URL and revision are specified, exports SVN project
       
    27 #    to <svn_path> directory.
       
    28 # 2) Creates S60 distribution policies to <svn_path> directory
       
    29 #    using setpolicyfiles.py script.
       
    30 # 3) Makes CCM sync and reconf for the CCM project.
       
    31 # 4) Creates CCM default task.
       
    32 # 5) Synchronizes the <svn_path> and <ccm_path> directories.
       
    33 # 6) If there were any changes, reconciles CCM project to database
       
    34 #    and commits CCM default task. If there were no changes, leaves
       
    35 #    the CCM default task open.
       
    36 #
       
    37 # The script execution aborts immediately if any error occurs.
       
    38 # In this case the user must manually delete the ccm task and
       
    39 # reconcile the ccm work area so that it is again
       
    40 # in the same level as it was before script execution.
       
    41 #
       
    42 # Usage:
       
    43 # python -u svn2ccm.py [-c "<comment_string>"] [-u <url>] [-r <revision>] <svn_path> <ccm_path> <ccm_project_id> <ccm_project_release>
       
    44 # For example:
       
    45 # python -u svn2ccm.py -c "Update from SVN release 7010" -u http://host/path -r 7010 X:\java C:\ccm_wa\java jrt-java s60_release > svn2ccm.log 2>&1
       
    46 #
       
    47 
       
    48 import datetime, filecmp, os, re
       
    49 import shutil, subprocess, stat, sys, traceback, zlib
       
    50 from optparse import OptionParser
       
    51 
       
    52 class CcmCounter:
       
    53     def __init__(self):
       
    54         self.added_dirs = 0
       
    55         self.added_files = 0
       
    56         self.removed_dirs = 0
       
    57         self.removed_files = 0
       
    58         self.modified_files = 0
       
    59         self.unmodified_files = 0
       
    60         self.visited_dirs = -1; # Do not count the root dir.
       
    61         self.start_time = datetime.datetime.now()
       
    62         self.stop_time = None
       
    63     def __str__(self):
       
    64         if self.stop_time is None:
       
    65             self.stop_time = datetime.datetime.now()
       
    66         msg = "Execution time " + str(self.stop_time - self.start_time) + "\n"
       
    67         msg += "Summary of CCM changes:\n"
       
    68         msg += "Added dirs: " + str(self.added_dirs) + "\n"
       
    69         msg += "Added files: " + str(self.added_files) + "\n"
       
    70         msg += "Removed dirs: " + str(self.removed_dirs) + "\n"
       
    71         msg += "Removed files: " + str(self.removed_files) + "\n"
       
    72         msg += "Modified files: " + str(self.modified_files) + "\n"
       
    73         msg += "Unmodified files: " + str(self.unmodified_files) + "\n"
       
    74         msg += "Total number of dirs: " + str(self.visited_dirs) + "\n"
       
    75         msg += "Total number of files: "
       
    76         msg += str(self.added_files + self.unmodified_files + self.modified_files)
       
    77         return msg
       
    78     def changes_made(self):
       
    79         if self.added_dirs > 0 or self.added_files > 0 or \
       
    80                 self.removed_dirs > 0 or self.removed_files > 0 or \
       
    81                 self.modified_files > 0:
       
    82             return True
       
    83         else:
       
    84             return False
       
    85 
       
    86 # Filetype mappings which override the default ones for Synergy.
       
    87 ccm_filetypes = {
       
    88     "\.cdtproject$": "xml",
       
    89     "\.checkstyle$": "xml",
       
    90     "\.classpath$": "xml",
       
    91     "\.classpath_qt$": "xml",
       
    92     "\.classpath_qt_j2me$": "xml",
       
    93     "\.confml$": "xml",
       
    94     "\.cproject$": "xml",
       
    95     "\.crml$": "xml",
       
    96     "\.gcfml$": "xml",
       
    97     "\.dr$": "binary",
       
    98     "\.der$": "binary",
       
    99     "\.flm$": "makefile",
       
   100     "\.javaversion$": "ascii",
       
   101     "\.jupiter$": "xml",
       
   102     "\.launch$": "xml",
       
   103     "\.meta$": "makefile",
       
   104     "\.metadata$": "ascii",
       
   105     "\.mmh$": "makefile",
       
   106     "\.odc$": "ascii",
       
   107     "\.plugin$": "ascii",
       
   108     "\.prefs$": "ascii",
       
   109     "\.pri$": "ascii",
       
   110     "\.prj$": "makefile",
       
   111     "\.prf$": "ascii",
       
   112     "\.project$": "xml",
       
   113     "\.project_classpath_builder$": "xml",
       
   114     "\.project_normal$": "xml",
       
   115     "\.review$": "xml",
       
   116     "\.ser$": "binary",
       
   117     "\.sps$": "ascii",
       
   118     "\.vcs$": "ascii",
       
   119     "cc_launch$": "ascii",
       
   120     "findtr$": "ascii",
       
   121     "launch$": "ascii",
       
   122     "new$": "ascii",
       
   123     "option$": "ascii",
       
   124     "syncqt$": "ascii",
       
   125     "unknowncert$": "binary",
       
   126     "IJG_README$": "ascii",
       
   127     "README$": "ascii",
       
   128     "package-list$": "html",
       
   129 }
       
   130 
       
   131 # Counter for ccm changes.
       
   132 ccm_counter = CcmCounter()
       
   133 
       
   134 # Command line options.
       
   135 opts = None
       
   136 
       
   137 def main():
       
   138     global opts, ccm_counter
       
   139     # Parse command line options and arguments.
       
   140     parser = OptionParser(
       
   141         usage = "python -u %prog [options] <svn_path> <ccm_path> <ccm_project_id> <ccm_project_release>")
       
   142     parser.add_option("-c", "--comment", dest="ccm_comment",
       
   143                       help="comment string for CCM objects")
       
   144     parser.add_option("-d", "--description_file", dest="ccm_description_file",
       
   145                       help="description file for CCM task")
       
   146     parser.add_option("-i", "--ignore", dest="ignore", action="append",
       
   147                       help="dir or file name to be ignored in the root directory")
       
   148     parser.add_option("--ignore_all", dest="ignore_all", action="append",
       
   149                       help="dir or file name to be ignored in all directories")
       
   150     parser.add_option("--remove_dir", dest="remove_dir", action="append",
       
   151                       help="before synchronising directories, remove specified dir from <svn_path>")
       
   152     parser.add_option("--no_commit", dest="no_commit",
       
   153                       action="store_true", default=False,
       
   154                       help="do not commit CCM task in the end")
       
   155     parser.add_option("--no_dp", dest="no_dp",
       
   156                       action="store_true", default=False,
       
   157                       help="do not generate distribution policies")
       
   158     parser.add_option("--no_exe", dest="no_exe",
       
   159                       action="store_true", default=False,
       
   160                       help="do not execute any SVN and CCM commands, only print them")
       
   161     parser.add_option("--no_reconf", dest="no_reconf",
       
   162                       action="store_true", default=False,
       
   163                       help="do not reconfigure CCM project")
       
   164     parser.add_option("--no_sync", dest="no_sync",
       
   165                       action="store_true", default=False,
       
   166                       help="do not sync CCM project")
       
   167     parser.add_option("-u", "--url", dest="svn_url",
       
   168                       help="SVN project URL")
       
   169     parser.add_option("-r", "--revision", dest="svn_revision",
       
   170                       help="SVN project revision")
       
   171     parser.add_option("-q", "--quiet", dest="quiet",
       
   172                       action="store_true", default=False,
       
   173                       help="quieter output")
       
   174     (opts, args) = parser.parse_args()
       
   175     if opts.ignore is None:
       
   176         opts.ignore = []
       
   177     if opts.ignore_all is None:
       
   178         opts.ignore_all = []
       
   179     if opts.remove_dir is None:
       
   180         opts.remove_dir = []
       
   181     svn_path = args[0]
       
   182     ccm_path = args[1]
       
   183     ccm_project_id = args[2]
       
   184     ccm_project_release = args[3]
       
   185     if len(args) > 4:
       
   186         print "ERROR: Too many arguments"
       
   187         sys.exit(1)
       
   188 
       
   189     # Check the validity of options and arguments.
       
   190     if opts.svn_revision and opts.svn_url and os.path.exists(svn_path):
       
   191         print "SVN2CCM: ERROR: SVN URL and revision specified but %s already exists" % svn_path
       
   192         sys.exit(1);
       
   193     if not os.path.isdir(ccm_path):
       
   194         print "SVN2CCM: ERROR: %s is not a directory" % ccm_path
       
   195         sys.exit(1);
       
   196     if not opts.svn_url and opts.svn_revision:
       
   197         print "SVN2CCM: ERROR: Must specify SVN URL for the revision"
       
   198         sys.exit(1);
       
   199     if not opts.svn_revision and opts.svn_url:
       
   200         print "SVN2CCM: ERROR: Must specify revision for the SVN URL"
       
   201         sys.exit(1);
       
   202     if not opts.ccm_comment and not opts.svn_revision:
       
   203         print "SVN2CCM: ERROR: Must specify either CCM comment or SVN URL and revision"
       
   204         sys.exit(1);
       
   205 
       
   206     try:
       
   207         print "SVN2CCM: Started " + str(ccm_counter.start_time)
       
   208         print "SVN2CCM: Arguments " + str(sys.argv)
       
   209         if opts.svn_revision and opts.svn_url:
       
   210             # Export SVN project.
       
   211             print "SVN2CCM: Exporting %s revision %s to %s" % \
       
   212                 (opts.svn_url, opts.svn_revision, svn_path)
       
   213             execute(["svn", "export"] + get_quiet_option() + ["-r", opts.svn_revision, quote_str(opts.svn_url), quote_str(svn_path)])
       
   214 
       
   215         # Check if there are directories which must be removed.
       
   216         for dir in opts.remove_dir:
       
   217             dir_path = os.path.join(svn_path, dir)
       
   218             print "SVN2CCM: Removing %s" % dir_path
       
   219             shutil.rmtree(dir_path)
       
   220 
       
   221         if not opts.no_dp:
       
   222             # Create S60 distribution policy files.
       
   223             # Set arguments to setpolicyfiles.main() with sys.argv.
       
   224             old_args = sys.argv
       
   225             sys.argv = ["setpolicyfiles.py", svn_path]
       
   226             print "SVN2CCM: Calling", sys.argv
       
   227             import setpolicyfiles
       
   228             setpolicyfiles.main()
       
   229             sys.argv = old_args
       
   230 
       
   231         if not opts.no_sync:
       
   232             # Sync CCM project.
       
   233             print "SVN2CCM: Syncing CCM project", datetime.datetime.now()
       
   234             execute(["ccm", "sync", "-p", quote_str(ccm_project_id)])
       
   235 
       
   236         if not opts.no_reconf:
       
   237             # Reconfigure CCM project.
       
   238             print "SVN2CCM: Reconfiguring CCM project", datetime.datetime.now()
       
   239             execute(["ccm", "reconf", "-r", "-p", quote_str(ccm_project_id)])
       
   240 
       
   241         # Create CCM task.
       
   242         print "SVN2CCM: Creating CCM task", datetime.datetime.now()
       
   243         if opts.ccm_description_file:
       
   244             execute(["ccm", "task", "-create", "-default", "-release", quote_str(ccm_project_release), "-descriptionfile", quote_str(opts.ccm_description_file), "-synopsis", get_comment_string()])
       
   245         else:
       
   246             execute(["ccm", "task", "-create", "-default", "-release", quote_str(ccm_project_release), "-description", get_comment_string(), "-synopsis", get_comment_string()])
       
   247 
       
   248         # Synchronize the SVN and CCM directories.
       
   249         print "SVN2CCM: Synchronizing from %s to %s %s" % \
       
   250             (svn_path, ccm_path, str(datetime.datetime.now()))
       
   251         if len(opts.ignore) > 0:
       
   252             print "SVN2CCM: Ignoring from root directory: " + ", ".join(opts.ignore)
       
   253         if len(opts.ignore_all) > 0:
       
   254             print "SVN2CCM: Ignoring from all directories: " + ", ".join(opts.ignore_all)
       
   255         sync_dirs(svn_path, ccm_path, opts.ignore, opts.ignore_all + [".svn"])
       
   256 
       
   257         if ccm_counter.changes_made():
       
   258             # Reconcile CCM project.
       
   259             print "SVN2CCM: Reconciling CCM project", datetime.datetime.now()
       
   260             execute(["ccm", "reconcile", "-r", "-cu", "-mwaf", "-update_db", "-p", quote_str(ccm_project_id)])
       
   261             if opts.no_commit:
       
   262                 print "SVN2CCM: WARNING: No commit, leaving CCM task open"
       
   263             else:
       
   264                 # Commit the default CCM task.
       
   265                 print "SVN2CCM: Committing CCM task", datetime.datetime.now()
       
   266                 execute(["ccm", "task", "-ci", "default"])
       
   267         else:
       
   268             # No changes, do not reconcile or commit.
       
   269             print "SVN2CCM: WARNING: No changes, leaving CCM task open"
       
   270 
       
   271         # Finished.
       
   272         ccm_counter.stop_time = datetime.datetime.now()
       
   273         print "SVN2CCM: Finished " + str(ccm_counter.stop_time)
       
   274         print str(ccm_counter)
       
   275     except:
       
   276         print "SVN2CCM: ERROR: Unexpected exception", datetime.datetime.now()
       
   277         traceback.print_exc()
       
   278         print str(ccm_counter)
       
   279         sys.exit(1)
       
   280 
       
   281 def sync_dirs(source, target, ignore, ignore_all):
       
   282     global ccm_counter
       
   283     ccm_counter.visited_dirs += 1
       
   284     dircmp = filecmp.dircmp(source, target, ignore + ignore_all)
       
   285     filelist = get_filelist(target)
       
   286     # Update the common files from source to target.
       
   287     for p in dircmp.common:
       
   288         sp = os.path.join(source, p)
       
   289         tp = os.path.join(target, p)
       
   290         tp_old = os.path.join(target, get_filename_case(p, filelist))
       
   291         # Check if file has been changed to be a dir or vice versa,
       
   292         # or if the dir/file name case has been changed.
       
   293         add_target = False
       
   294         if os.path.isdir(sp) and os.path.isfile(tp_old):
       
   295             add_target = True
       
   296             remove_ccm_file(tp_old)
       
   297         elif os.path.isfile(sp) and os.path.isdir(tp_old):
       
   298             add_target = True
       
   299             remove_ccm_dir(tp_old)
       
   300         elif not tp == tp_old:
       
   301             add_target = True
       
   302             if os.path.isdir(tp_old):
       
   303                 remove_ccm_dir(tp_old)
       
   304             else:
       
   305                 remove_ccm_file(tp_old)
       
   306         # Synchronize existing dir or update existing file.
       
   307         if os.path.isdir(sp):
       
   308             if add_target:
       
   309                 add_dir(tp)
       
   310             sync_dirs(sp, tp, [], ignore_all)
       
   311         else:
       
   312             if add_target:
       
   313                 add_file(sp, tp)
       
   314             elif not files_equal(sp, tp):
       
   315                 # Files are different, make an update.
       
   316                 update_file(sp, tp)
       
   317             else:
       
   318                 # Files are identical, update counter.
       
   319                 ccm_counter.unmodified_files += 1
       
   320     # Add files which only exist in source.
       
   321     for p in dircmp.left_only:
       
   322         sp = os.path.join(source, p)
       
   323         tp = os.path.join(target, p)
       
   324         if os.path.isdir(sp):
       
   325             add_dir(tp)
       
   326             sync_dirs(sp, tp, [], ignore_all)
       
   327         else:
       
   328             add_file(sp, tp)
       
   329     # Remove files which only exist in target.
       
   330     for p in dircmp.right_only:
       
   331         sp = os.path.join(source, p)
       
   332         tp = os.path.join(target, p)
       
   333         if os.path.isdir(tp):
       
   334             remove_dir(tp)
       
   335         else:
       
   336             remove_file(tp)
       
   337 
       
   338 def add_dir(target):
       
   339     print "Add dir " + target
       
   340     os.mkdir(target)
       
   341     global ccm_counter
       
   342     ccm_counter.added_dirs += 1
       
   343 
       
   344 def add_file(source, target):
       
   345     print "Add file " + target
       
   346     shutil.copy(source, target)
       
   347     global ccm_counter
       
   348     ccm_counter.added_files += 1
       
   349 
       
   350 def update_file(source, target):
       
   351     print "Update file " + target
       
   352     os.chmod(target, stat.S_IWRITE);
       
   353     shutil.copy(source, target)
       
   354     global ccm_counter
       
   355     ccm_counter.modified_files += 1
       
   356 
       
   357 def remove_dir(target):
       
   358     print "Remove dir " + target
       
   359     remove_read_only_attribute(target)
       
   360     shutil.rmtree(target)
       
   361     global ccm_counter
       
   362     ccm_counter.removed_dirs += 1
       
   363 
       
   364 def remove_file(target):
       
   365     if os.path.basename(target) == "_ccmwaid.inf":
       
   366         # Do not remove CCM special files.
       
   367         return
       
   368     print "Remove file " + target
       
   369     os.chmod(target, stat.S_IWRITE);
       
   370     os.remove(target)
       
   371     ccm_counter.removed_files += 1
       
   372 
       
   373 def remove_ccm_dir(target):
       
   374     execute(["ccm", "unuse", quote_str(target)])
       
   375     global ccm_counter
       
   376     ccm_counter.removed_dirs += 1
       
   377 
       
   378 def remove_ccm_file(target):
       
   379     if os.path.basename(target) == "_ccmwaid.inf":
       
   380         # Do not remove CCM special files.
       
   381         return
       
   382     execute(["ccm", "unuse", quote_str(target)])
       
   383     ccm_counter.removed_files += 1
       
   384 
       
   385 def remove_read_only_attribute(target):
       
   386     os.chmod(target, stat.S_IWRITE)
       
   387     if os.path.isdir(target):
       
   388         for f in os.listdir(target):
       
   389             new_target = os.path.join(target,f)
       
   390             remove_read_only_attribute(new_target)
       
   391 
       
   392 def get_comment_option():
       
   393     global opts
       
   394     comment_str = get_comment_string()
       
   395     if comment_str is None:
       
   396         return []
       
   397     else:
       
   398         return ["-c", comment_str]
       
   399 
       
   400 def get_comment_string():
       
   401     global opts
       
   402     if opts.ccm_comment is None:
       
   403         if opts.svn_revision is None:
       
   404             return None
       
   405         else:
       
   406             return quote_str("Update from SVN revision %s" % opts.svn_revision)
       
   407     else:
       
   408         return quote_str(opts.ccm_comment)
       
   409 
       
   410 def get_quiet_option():
       
   411     global opts
       
   412     if opts.quiet:
       
   413         return ["-q"]
       
   414     else:
       
   415         return []
       
   416 
       
   417 def get_filetype_option(path):
       
   418     global ccm_filetypes
       
   419     for ext, type in ccm_filetypes.iteritems():
       
   420         if re.search(ext, path, re.I):
       
   421             return ["-type", type]
       
   422     return []
       
   423 
       
   424 def execute(args):
       
   425     global opts, ccm_counter
       
   426     cmd = " ".join(args)
       
   427     if not opts.quiet:
       
   428         print cmd
       
   429     if opts.no_exe:
       
   430         return
       
   431     retcode = subprocess.call(cmd)
       
   432     if retcode != 0:
       
   433         print "SVN2CCM: ERROR: Command '%s' failed with code %d" % (cmd, retcode)
       
   434         sys.exit(retcode)
       
   435 
       
   436 def get_filelist(path):
       
   437     if os.path.isdir(path):
       
   438         return os.listdir(path)
       
   439     else:
       
   440         return None
       
   441 
       
   442 def get_filename_case(filename, filelist):
       
   443     filename = os.path.basename(filename)
       
   444     if filelist is None:
       
   445         result = [filename]
       
   446     else:
       
   447         result = [f for f in filelist if f.lower() == filename.lower()]
       
   448     return result[0]
       
   449 
       
   450 def files_equal(source, target):
       
   451     if digest(source) == digest(target):
       
   452         return True
       
   453     else:
       
   454         return False
       
   455 
       
   456 def digest(file):
       
   457     f = open(file, "rb")
       
   458     try:
       
   459         h = zlib.crc32(f.read())
       
   460     finally:
       
   461         f.close()
       
   462     return h
       
   463 
       
   464 def quote_str(s):
       
   465     return '"' + str(s) + '"'
       
   466 
       
   467 if __name__ == "__main__":
       
   468     main()