|
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() |