1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """ CM/Synergy Python toolkit.
21
22 """
23
24 import logging
25 import netrc
26 import os
27 import re
28 import subprocess
29 import sys
30 import threading
31
32 import fileutils
33 import nokia.gscm
34
35
36
37 _logger = logging.getLogger("ccm")
38
39
40
41 VALID_OBJECT_STATES = ('working', 'checkpoint', 'public', 'prep', 'integrate', 'sqa', 'test','released')
42 STATIC_OBJECT_STATES = ('integrate', 'sqa', 'test','released')
43 CCM_SESSION_LOCK = os.path.join(os.environ['TEMP'], "ccm_session.lock")
44
46 """ Runs a command and returns the result data. """
47 launcher = os.path.join(os.environ['HELIUM_HOME'], 'tools', 'common', 'python', 'scripts', 'timeout_launcher.py')
48 targ = ""
49 if timeout is not None:
50 targ = "--timeout=%s" % timeout
51 process = subprocess.Popen("python %s %s -- %s" % (launcher, targ, command), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
52 stdout = process.communicate()[0]
53 process.wait()
54 _logger.debug(stdout)
55 _logger.debug("Return code: %s" % process.returncode)
56 return (stdout, process.returncode)
57
58
60 """ Base exception that should be raised by methods of this framework. """
61 - def __init__(self, reason, result = None):
62 Exception.__init__(self, reason)
63 self.result = result
64
65
67 """Class that abstracts ccm call result handling.
68
69 Subclass it to implement a new generic output parser.
70 """
72 self._session = session
73 self.status = None
74 self._output = None
75 self._output_str = None
76
79
81 """ Internal function to allow overloading, you must override _setoutput.
82 """
83
84 if isinstance(output, unicode):
85 self._output_str = output.encode('ascii', 'replace')
86 else:
87 self._output_str = output.decode('ascii', 'ignore')
88 _logger.debug("output ---->")
89 for line in self._output_str.splitlines():
90 _logger.debug(line)
91 _logger.debug("<----")
92 self._setoutput(self._output_str)
93
95 """ Returns the content of _output. """
96 return self._output
97
99 """ Synergy output log. """
100 return self._output_str.encode('ascii', 'replace')
101
102 output = property(_getoutput, __setoutput)
103
104
106 """ Project checkout output parser.
107 Sets project to the created project or None if failed.
108 """
113
115 """ Parsing the output of the checkout command. """
116 self._output = output
117 for line in output.splitlines():
118 mresult = re.match(r"Saved work area options for project: '(.+)'", line, re.I)
119
120 if mresult != None:
121
122 self.__result_project = self._session.create(mresult.group(1))
123 _logger.debug("ProjectCheckoutResult: project: '%s'" % self.__result_project)
124 return
125
127 """ return the checked out project. """
128 return self.__result_project
129
130 project = property(__get_result_project)
131
132
134 """ Parses purpose query output. """
137
139 self._output = {}
140 for line in output.splitlines():
141 mresult = re.match(r"(?P<purpose>.+?)\s+(?P<member_status>\w+)\s+(?P<status>\w+)$", line)
142 if mresult != None:
143 data = mresult.groupdict()
144 if re.match(r'^\s+Purpose\s+Member$', data['purpose'], re.I) == None:
145 self._output[data['purpose'].strip()] = {'member_status' : data['member_status'].strip(),
146 'status' : data['status'].strip()
147 }
148
150 """ Parses purpose query output. """
153
155 self._output = {}
156 project = None
157
158 for line in output.splitlines():
159 mresult = re.match(r"Project:\s*(.+)\s*$", line)
160 if mresult != None:
161 project = self._session.create(mresult.group(1))
162 self._output[project] = []
163 mresult = re.match(r"^(.*)\s+(\w+#\d+)\s+(.+)$", line)
164 if mresult != None and project != None:
165 self._output[project].append({'object': self._session.create(mresult.group(1)),
166 'task': self._session.create("Task %s" % mresult.group(2)),
167 'comment': mresult.group(3)})
168 mresult = re.match(r"^(\w+#\d+)\s+(.+)$", line)
169 if mresult != None and project != None:
170 self._output[project].append({'task': self._session.create("Task %s" % mresult.group(1)),
171 'comment': mresult.group(2)})
172
173
175 """ Parses finduse query output. """
179
181 self._output = []
182 for line in output.splitlines():
183 _logger.debug("FinduseResult: ---->%s<----" % line)
184 _logger.debug("FinduseResult: ---->%s-%s<----" % (self.__object.name, self.__object.version))
185
186
187 mresult = re.match(r"^\s*(?P<path>.+)[\\/]%s-%s@(?P<project>.+)" % (self.__object.name, self.__object.version), line, re.I)
188 if mresult != None:
189 data = mresult.groupdict()
190 _logger.debug("FinduseResult: %s" % data)
191 project = self._session.create(data['project'])
192 self._output.append({'path' : data['path'], 'project' : project})
193
194
233
234
236 """ Parse update template refresh output. """
239
241 self._output = {'added': [], 'removed': []}
242 match_added = re.compile(r"^Added the following tasks")
243 match_removed = re.compile(r"^Removed the following tasks")
244 match_task_new = re.compile(r"^\s+(Task \S+#\d+)")
245 section = None
246
247 for line in output.splitlines():
248 res = match_added.match(line)
249 if res != None:
250 section = 'added'
251 continue
252 res = match_removed.match(line)
253 if res != None:
254 section = 'removed'
255 continue
256 if section is not None:
257 res = match_task_new.match(line)
258 if res != None:
259 self._output[section].append(self._session.create(res.group(1)))
260 continue
261
262
264 """ Parse update output. """
268
270 self._output = output
271 match_failed = re.compile(r"(Update failed)")
272 for line in output.splitlines():
273 res = match_failed.match(line)
274 if res != None:
275 self._success = False
276
277 @property
280
282 """ Parse update output. """
285
287 self._output = {"tasks":[], "modifications": [], "errors": [], "warnings": []}
288 match_object_update = re.compile(r"^\s+'(.*)'\s+replaces\s+'(.*)'\s+under\s+'(.*)'\.")
289 match_object_new = re.compile(r"^\s+(?:Subproject\s+)?'(.*)'\s+is now bound under\s+'(.*)'\.")
290 match_task_new = re.compile(r"^\s+(Task \S+#\d+)")
291 match_no_candidate = re.compile(r"^\s+(.+) in project (.+) had no candidates")
292 match_update_failure = re.compile(r"^\s+Failed to use selected object\s+(.+)\s+under directory\s+(.+)\s+in project\s+(.+)\s+:\s+(.+)")
293 match_warning = re.compile(r"^Warning:(.*)")
294 match_failed = re.compile(r"(Update failed)")
295
296
297 for line in output.splitlines():
298 _logger.info(line)
299 res = match_object_update.match(line)
300 if res != None:
301 self._output['modifications'].append({ "new": self._session.create(res.group(1)),
302 "old": self._session.create(res.group(2)),
303 "project": self._session.create(res.group(3))
304 })
305 continue
306 res = match_object_new.match(line)
307 if res != None:
308 self._output['modifications'].append({ "new": self._session.create(res.group(1)),
309 "old": None,
310 "project": self._session.create(res.group(2))
311 })
312 continue
313 res = match_task_new.match(line)
314 if res != None:
315 self._output['tasks'].append(self._session.create(res.group(1)))
316 continue
317 res = match_no_candidate.match(line)
318 if res != None:
319 self._output['errors'].append({'family': res.group(1),
320 'project': self._session.create(res.group(2)),
321 'comment': "had no candidates",
322 'line': line,
323 })
324 continue
325 res = match_update_failure.match(line)
326 if res != None:
327 self._output['errors'].append({'family': res.group(1),
328 'dir': self._session.create(res.group(2)),
329 'project': self._session.create(res.group(3)),
330 'comment': res.group(4),
331 'line': line,
332 })
333 continue
334 res = match_warning.match(line)
335 if res != None:
336 self._output['warnings'].append({'family': None,
337 'project': None,
338 'comment': res.group(1),
339 'line': line,
340 })
341 continue
342 res = match_failed.match(line)
343 if res != None:
344 self._success = False
345 self._output['errors'].append({'Serious': res.group(1),
346 })
347 continue
348
349
350
352 """ Parse work area info output. """
355
357 """ Returns a dict with the following fields:
358 * project: a ccm.Project instance
359 * maintain: a boolean
360 * copies: a boolean
361 * relative: a boolean
362 * time: a boolean
363 * translate: a boolean
364 * modify: a boolean
365 * path: a string representing the project wa path
366 """
367 self._output = None
368 for line in output.splitlines():
369 mresult = re.match(r"(?P<project>.*)\s+(?P<maintain>TRUE|FALSE)\s+(?P<copies>TRUE|FALSE)\s+(?P<relative>TRUE|FALSE)\s+(?P<time>TRUE|FALSE)\s+(?P<translate>TRUE|FALSE)\s+(?P<modify>TRUE|FALSE)\s+'(?P<path>.*)'", line)
370 if mresult != None:
371 data = mresult.groupdict()
372 self._output = {'project': self._session.create(data['project']),
373 'maintain' : data['maintain'] == "TRUE",
374 'copies' : data['copies'] == "TRUE",
375 'relative' : data['relative'] == "TRUE",
376 'time' : data['time'] == "TRUE",
377 'translate' : data['translate'] == "TRUE",
378 'modify' : data['modify'] == "TRUE",
379 'path' : data['path']
380 }
381 return
382
383
385
388
390 self._output = None
391 for line in output.splitlines():
392 mresult = re.match(r"Task\s+(?P<task>\S+\#\d+)\s+created\.", line)
393 if mresult != None:
394 self._output = self._session.create("Task " + mresult.groupdict()['task'])
395 return
396
397
399 """ Class that abstract ccm call result handling.
400 Subclass it to implement a new generic output parser.
401 """
404
406 def _create(arg):
407 mresult = re.match(r"^\s*(?P<name>\w+)", arg.strip())
408 if mresult != None:
409 return mresult.groupdict()['name']
410 return None
411 self._output = [_create(line) for line in obj.strip().splitlines()]
412
413
415 """ Parses an object list Synergy output. """
418
420 self._output = []
421 if re.match(r"^None|^No tasks|^Warning", obj, re.M) != None:
422 return
423 def _create(arg):
424 arg = arg.strip()
425 if arg != "":
426 return self._session.create(arg)
427 return None
428 result = [_create(line) for line in obj.strip().splitlines()]
429 for result_line in result:
430 if result_line != None:
431 self._output.append(result_line)
432
434 """ Parses an object list Synergy output. """
435
436 dataconv = {'ccmobject': lambda x, y: x.create(y),
437 'string': lambda x, y: y,
438 'int': lambda x, y: int(y),
439 'boolean': lambda x, y: (y.lower() == "true")}
440
441 - def __init__(self, session, separator, keywords, datamodel):
442 self._separator = separator
443 self._keywords = keywords
444 self._datamodel = datamodel
445 Result.__init__(self, session)
446
450
452 regex_keywords = [r'%s%s%s(.*?)' % (self._separator, x, self._separator) for x in self._keywords]
453 regex = r''.join(regex_keywords)
454 regex = r"%s%s\s*\n" % (regex, self._separator)
455 return re.compile(regex, re.MULTILINE | re.I | re.DOTALL | re.VERBOSE | re.U)
456
458 self._output = []
459 regex = self.regex()
460 _logger.debug("Regex %s" % (regex.pattern))
461 for match in regex.finditer(obj):
462 _logger.debug("Found: %s" % (match))
463 if match != None:
464 output_line = {}
465 for i in range(len(self._datamodel)):
466 _logger.debug("Found %d: %s" % (i, match.group(i + 1)))
467 model = self._datamodel[i]
468 output_line[self._keywords[i]] = self.dataconv[model](self._session, match.group(i + 1))
469 i += 1
470 self._output.append(output_line)
471
472
474 """ Parses a folder copy result """
477
479 self._output = None
480 for line in output.splitlines():
481 mo = re.match(r"appended to", line)
482 if mo != None:
483 self._output = self._session.create(line)
484 return
485
486 CHECKOUT_LOG_RULES = [[r'^Derive failed for', logging.ERROR],
487 [r'^Serious:', logging.ERROR],
488 [r'^Warning: .* failed.', logging.ERROR],
489 [r'^Warning:', logging.WARNING],]
490
491 UPDATE_LOG_RULES = [[r'^Update failed.', logging.ERROR],
492 [r'^Serious:', logging.ERROR],
493 [r'^\s+Failed to', logging.ERROR],
494 [r'^\d+ failures to', logging.ERROR],
495 [r"^Warning: This work area '.+' cannot be reused", logging.ERROR],
496 [r'^Rebind of .* failed', logging.ERROR],
497 [r'^Warning: .* failed.', logging.ERROR],
498 [r'^Skipping \'.*\'\. You do not have permission to modify this project.', logging.ERROR],
499 [r'^Work area conflict exists for file', logging.ERROR],
500 [r'^Warning: No candidates found for directory entry', logging.ERROR],
501 [r'^Warning:', logging.WARNING],]
502
503 CONFLICTS_LOG_RULES = [[r'^\w+#\d+\s+Implicit', logging.WARNING],
504 [r'^(.*)\s+(\w+#\d+)\s+(.+)', logging.ERROR],]
505
506 SYNC_LOG_RULES = [[r'^\s+0\s+Conflict\(s\) for project', logging.INFO],
507 [r'^\s+\d+\s+Conflict\(s\) for project', logging.ERROR],
508 [r'^Project \'.*\' does not maintain a workarea.', logging.ERROR],
509 [r'^Work area conflict exists for file', logging.ERROR],
510 [r'^Warning: Conflicts detected during synchronization. Check your logs.', logging.ERROR],
511 [r'^Warning:', logging.WARNING],]
512
514 """ Rules it a list of tuple defining a regular expression and an log level. """
515 if logger is None:
516 logger = _logger
517 crules = []
518 if rules is not None:
519 for rule in rules:
520 crules.append([re.compile(rule[0]), rule[1]])
521
522 for line in str(result).splitlines():
523 for rule in crules:
524 if rule[0].match(line) != None:
525 logger.log(rule[1], line)
526 break
527 else:
528 logger.info(line)
529
531 """An abstract Synergy session.
532
533 Must be overridden to implement either a single session or
534 multiple session handling.
535 """
536 - def __init__(self, username, engine, dbpath, ccm_addr):
537 self.username = username
538 self.engine = engine
539 self.dbpath = dbpath
540 self._session_addr = ccm_addr
541
542 self.__ccm_objects = {}
543
545 """ Returns the Synergy session id."""
546 return self._session_addr
547
549 _logger.debug("AbstractSession: database")
550 self.__find_dbpath()
551 _logger.debug("AbstractSession: database: %s" % self.dbpath)
552 return os.path.basename(self.dbpath)
553
555 """ retrieve the database path from current session status. """
556 _logger.debug("AbstractSession: __find_dbpath")
557 if (self.dbpath != None):
558 return
559 result = self.execute("status")
560 for match in re.finditer(r'(?:(?:Graphical)|(?:Command)) Interface\s+@\s+(?P<ccmaddr>\w+:\d+(?:\:\d+\.\d+\.\d+\.\d+)+)(?P<current_session>\s+\(current\s+session\))?\s*\nDatabase:\s*(?P<dbpath>\S+)', result.output, re.M | re.I):
561 d = match.groupdict()
562 if (d['current_session'] != None):
563 _logger.debug("AbstractSession: __find_dbpath: Found dbpath: %s" % d['dbpath'])
564 self.dbpath = d['dbpath']
565 assert self.dbpath != None
566
567 - def execute(self, _, result=None):
568 """ Abstract function that should implement the execution of ccm command
569 line call.
570 """
571 return result
572
574 """ Object factory, this is the toolkit entry point to create objects from
575 four part names. Objects are stored into a dictionary, so you have
576 only one wrapper per synergy object.
577 """
578 result = re.search(r"^(?P<project>.+)-(?P<version>[^:]+?)$", fpn)
579 if result != None:
580 matches = result.groupdict()
581 fpn = "%s-%s:project:%s#1" % (matches['project'], matches['version'], self.database())
582 _logger.debug("session.create('%s')" % fpn)
583 ofpn = FourPartName(fpn)
584 if not self.__ccm_objects.has_key(str(fpn)):
585 obj = None
586 if ofpn.type == 'project':
587 obj = Project(self, fpn)
588 elif ofpn.type == 'dir':
589 obj = Dir(self, fpn)
590 elif ofpn.type == 'task':
591 obj = Task(self, fpn)
592 elif ofpn.type == 'folder':
593 obj = Folder(self, fpn)
594 elif ofpn.type == 'releasedef':
595 obj = Releasedef(self, fpn)
596 else:
597 obj = File(self, fpn)
598 self.__ccm_objects[str(fpn)] = obj
599 return self.__ccm_objects[str(fpn)]
600
602 """ Return a dictionary containing workarea info from directory dir.
603 """
604 if (not os.path.exists(dir_)):
605 raise CCMException("Error retrieving work_area info for the directory '%s' (doesn't exists)" % dir_)
606 path = os.path.abspath(os.path.curdir)
607 path_ccmwaid = os.path.join(dir_,"_ccmwaid.inf");
608 if(not os.path.exists(path_ccmwaid)):
609 raise CCMException("No work area in '%s'" % dir_)
610 os.chdir(dir_)
611 result = self.execute("wa -show", WorkAreaInfoResult(self))
612 os.chdir(path)
613 if result.output == None:
614 raise CCMException("Error retrieving work_area info for the directory '%s'" % dir_)
615 return result.output
616
618 result = self.execute("set role")
619 return result.output.strip()
620
622 """ method to be override by child class else property accession is not working properly. """
623 if len(role) == 0 or role == None:
624 raise CCMException("You must provide a role.")
625 result = self.execute("set role %s" % role)
626 if re.match(r'^Warning:', result.output, re.M) != None:
627 raise CCMException("Error switching to role %s: %s" %(role, result.output.strip()))
628
631
632 role = property(fget=_get_role, fset=_set_role)
633
635 result = self.execute("set Home")
636 return result.output.strip()
637
644
645 home = property(_get_home, _set_home)
646
649
651 self.__find_dbpath()
652 return self._session_addr + ':' + self.dbpath
653
656
659
661 """ Returns available purposes. """
662 args = ""
663 if role != None:
664 args = "-role \"%s\"" % role
665 result = self.execute("project_purpose -show %s" % args, ProjectPurposeResult(self))
666 return result.output
667
669 """A Synergy session.
670 """
671 - def __init__(self, username, engine, dbpath, ccm_addr, close_on_exit=True):
672 AbstractSession.__init__(self, username, engine, dbpath, ccm_addr)
673 self._execute_lock = threading.Lock()
674 self.close_on_exit = close_on_exit
675
676 @staticmethod
677 - def start(username, password, engine, dbpath, timeout=300):
678 if username == None:
679 raise CCMException('username is not valid')
680 if password == None:
681 raise CCMException('password is not valid')
682 if CCM_BIN == None:
683 raise CCMException("Could not find CM/Synergy executable in the path.")
684 command = "%s start -m -q -nogui -n %s -pw %s -h %s -d %s" % \
685 (CCM_BIN, username, password, engine, dbpath)
686 _logger.debug('Starting new session:' + command.replace(password, "***"))
687 (result, status) = _execute(command, timeout=timeout)
688 if status != 0:
689 raise Exception("Error creating a session: result:\n%s" % result)
690 session_addr = result.strip()
691 _logger.debug(session_addr)
692 if not re.match(r'\w+:\d+:\d+.\d+.\d+.\d+(:\d+.\d+.\d+.\d+)?', session_addr):
693 raise Exception("Error creating a session: result:\n%s" % result)
694 return Session(username, engine, dbpath, session_addr)
695
696 - def execute(self, cmdline, result=None):
697 """ Executes a Synergy CLI operation. """
698 if self._session_addr == None:
699 raise CCMException("No Synergy session running")
700 if CCM_BIN == None:
701 raise CCMException("Could not find CM/Synergy executable in the path.")
702 self._execute_lock.acquire()
703
704 try:
705 if result == None:
706 result = Result(self)
707 if sys.platform == "win32":
708 command = "set CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " %s" % cmdline
709 else:
710 command = "export CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " %s" % cmdline
711 _logger.debug('Execute > ' + command)
712
713 process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
714 output = process.stdout.read()
715 result.status = process.returncode
716 finally:
717 self._execute_lock.release()
718 result.output = output.strip()
719 return result
720
722 """ Closes this Synergy session if it was not previously running anyway. """
723 _logger.debug("Closing session %s" % self._session_addr)
724 if self._session_addr != None and self.close_on_exit:
725 _logger.debug("Closing session %s" % self._session_addr)
726 self._execute_lock.acquire()
727 if sys.platform == "win32":
728 command = "set CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " stop"
729 else:
730 command = "export CCM_ADDR=" + self._session_addr + " && " + CCM_BIN + " stop"
731 _logger.debug('Execute > ' + command)
732 pipe = os.popen(command)
733 pipe.close()
734 self._session_addr = None
735 self._execute_lock.release()
736 elif self._session_addr != None and not self.close_on_exit:
737 _logger.debug("Keeping session %s alive." % self._session_addr)
738
739
741 """ Session that transparently handled several subsession, to easily enable
742 multithreaded application.
743 """
744 - def __init__(self, username, password, engine, dbpath, database=None, size=4, opener=None):
745 AbstractSession.__init__(self, username, engine, dbpath, None)
746 self._opener = opener
747 if self._opener is None:
748 self._opener = open_session
749 self._free_sessions = []
750 self._used_sessions = []
751 self._thread_sessions = {}
752 self._pool_lock = threading.Condition()
753 self._lock_pool = False
754 self.__password = password
755 self.__database = database
756 self.size = size
757
759 """ Set the pool size """
760 self._pool_lock.acquire()
761 poolsize = len(self._free_sessions) + len(self._used_sessions)
762 if poolsize > size:
763 to_be_remove = poolsize - size
764 self._lock_pool = True
765 while len(self._free_sessions) < to_be_remove:
766 self._pool_lock.wait()
767 for _ in range(to_be_remove):
768 self._free_sessions.pop().close()
769 self._lock_pool = False
770 else:
771 for _ in range(size - poolsize):
772 self._free_sessions.append(self._opener(self.username, self.__password, self.engine, self.dbpath, self.__database, False))
773 self._pool_lock.release()
774
776 self._pool_lock.acquire()
777 poolsize = len(self._free_sessions) + len(self._used_sessions)
778 self._pool_lock.release()
779 return poolsize
780
781 size = property (_get_size, _set_size)
782
783 - def execute(self, cmdline, result=None):
784 """ Executing a ccm command on a free session. """
785 _logger.debug("SessionPool:execute: %s %s" % (cmdline, type(result)))
786
787
788 self._pool_lock.acquire()
789
790
791 if threading.currentThread() in self._thread_sessions:
792 _logger.debug("Same thread, reusing allocation session.")
793
794 self._pool_lock.release()
795 return self._thread_sessions[threading.currentThread()].execute(cmdline, result)
796
797 while len(self._free_sessions)==0 or self._lock_pool:
798 self._pool_lock.wait()
799 session = self._free_sessions.pop(0)
800 self._used_sessions.append(session)
801 self._thread_sessions[threading.currentThread()] = session
802 self._pool_lock.release()
803
804
805 try:
806 result = session.execute(cmdline, result)
807 finally:
808
809 self._pool_lock.acquire()
810 self._thread_sessions.pop(threading.currentThread())
811 self._used_sessions.remove(session)
812 self._free_sessions.append(session)
813 self._pool_lock.notifyAll()
814 self._pool_lock.release()
815 return result
816
818 """ Closing all subsessions. """
819 _logger.debug("Closing session pool sub-sessions")
820 self._lock_pool = True
821 self._pool_lock.acquire()
822 while len(self._used_sessions) > 0:
823 _logger.debug("Waiting to free used sessions.")
824 _logger.debug("Waiting to free used sessions. %s %s" % (len(self._used_sessions), len(self._free_sessions)))
825 _logger.debug(self._used_sessions)
826 _logger.debug(self._free_sessions)
827 self._pool_lock.wait()
828 _logger.debug("Closing all free session from the pool.")
829 while len(self._free_sessions) > 0:
830 self._free_sessions.pop().close()
831 self._lock_pool = False
832 self._pool_lock.notifyAll()
833 self._pool_lock.release()
834
836 """ Set role on all subsessions. """
837 self._lock_pool = True
838 self._pool_lock.acquire()
839 while len(self._used_sessions)!=0:
840 self._pool_lock.wait()
841
842 try:
843 for session in self._free_sessions:
844 session.role = role
845 finally:
846 self._lock_pool = False
847 self._pool_lock.notifyAll()
848 self._pool_lock.release()
849
850
852 """ This object wrap a synergy query, it takes a query as input as well as the
853 attribute you want as output, and get them translated using the model configuration.
854 e.g
855 Query(session, "type='task' and release='test/next'", ['objectname', 'task_synopsis'], ['ccmobject', 'string'])
856
857 This will return a list of hash: [{'objectname': Task(xxx), 'task_synopsis': 'xxx'}, ...]
858 """
859
860 - def __init__(self, session, query, keywords, model, cmd="query"):
861 """ Initialize a Synergy query."""
862 self._session = session
863 self._query = query
864 self._keywords = keywords
865 self._model = model
866 self._cmd = cmd
867
869 """ Executing the query on the database. """
870 mapper = DataMapperListResult(self._session, '@@@', self._keywords, self._model)
871 query = "%s %s -u -f \"%s\"" % (self._cmd, self._query, mapper.format())
872 return self._session.execute(query, mapper)
873
874
875
877 """ Badly formed Synergy four-part name. """
880
881
883 """ This class handle four part name parsing and validation.
884 """
885
887 """ Create a FourPartName object based on a ifpn string.
888
889 The string have to match the following patterns:
890 - name-version:type:instance
891 - name:version:releasedef:instance
892 - Task database#id
893 - Folder database#id
894
895 Anything else is considered as old release string format.
896
897 """
898 _logger.debug("FourPartName: '%s'", ifpn)
899 fpn = FourPartName.convert(ifpn)
900 result = re.search(r"^(?P<name>.+)-(?P<version>.+?):(?P<type>\S+):(?P<instance>\S+)$", fpn)
901 if result == None:
902 result = re.search(r"^(?P<name>.+):(?P<version>.+?):(?P<type>releasedef):(?P<instance>\S+)$", fpn)
903 if result == None:
904 raise InvalidFourPartNameException(fpn)
905
906 self._name = result.groupdict()['name']
907 self._version = result.groupdict()['version']
908 self._type = result.groupdict()['type']
909 self._instance = result.groupdict()['instance']
910
912 """ Returns the name of the object. """
913 return self._name
914
916 """ Returns the version of the object. """
917 return self._version
918
920 """ Returns the type of the object. """
921 return self._type
922
924 """ Returns the instance of the object. """
925 return self._instance
926
932
934 """ Returns the string representation of the object. """
935 return self.objectname
936
938 """ Returns the string representation of the python object. """
939 if (self.type == 'releasedef'):
940 return "<%s:%s:%s:%s>" % (self.name, self.version, self.type, self.instance)
941 return "<%s-%s:%s:%s>" % (self.name, self.version, self.type, self.instance)
942
944 """ Returns True if the ccmobject is part of the same family (=same name, type and version) as self. """
945 assert isinstance(ccmobject, FourPartName)
946 return (self.name == ccmobject.name and self.type == ccmobject.type and self.instance == ccmobject.instance)
947
950
952 """ Returns True if object four parts name are identical. """
953 if ccmobject == None:
954 return False
955 assert isinstance(ccmobject, FourPartName)
956 return (self.name == ccmobject.name and self.version == ccmobject.version and self.type == ccmobject.type and self.instance == ccmobject.instance)
957
959 """ Returns True if object four parts name are different. """
960 if ccmobject == None:
961 return True
962 assert isinstance(ccmobject, FourPartName)
963 return (self.name != ccmobject.name or self.version != ccmobject.version or self.type != ccmobject.type or self.instance != ccmobject.instance)
964
965 @staticmethod
967 """ Check if a given string represents a valid four part name.
968 """
969 return (re.match(r"^(.+)-(.+?):(\S+):(\S+)|(.+):(.+?):releasedef:(\S+)$", fpn) != None)
970
971 @staticmethod
973 """ Update a CCM output string to a valid four part name. This is due to the f$*@ing inconsistent
974 output of CM/Synergy that will probably never get fixed as it would require they hire humains
975 and not apes to core their CLI.
976 """
977 fpn = fpn.strip()
978 if FourPartName.is_valid(fpn):
979 return fpn
980 result = re.search(r"^(?P<type>Task|Folder)\s+(?P<instance>\w+)#(?P<id>\d+)$", fpn)
981 if result != None:
982 matches = result.groupdict()
983 if matches["type"] == "Task":
984 return "task%s-1:task:%s" % (matches["id"], matches["instance"])
985 elif matches["type"] == "Folder":
986 return "%s-1:folder:%s" % (matches['id'], matches['instance'])
987 else:
988 result = re.search(r"^(?P<project>\S+)/(?P<version>\S+)$", fpn)
989 if result != None:
990 matches = result.groupdict()
991 return "%s:%s:releasedef:1" % (matches['project'], matches['version'])
992 else:
993
994 result = re.search(r"^[^\s^:^-]+$", fpn)
995 if result != None:
996 return "none:%s:releasedef:1" % (fpn)
997 raise InvalidFourPartNameException(fpn)
998
999 name = property (__getname)
1000 version = property (__getversion)
1001 type = property (__gettype)
1002 instance = property (__getinstance)
1003 objectname = property (__getobjectname)
1004 family = property(__getfamily)
1005
1006
1008 """ Base class for any Synergy object. """
1009
1013
1015 return self._session
1016
1017 session = property(_getsession)
1018
1020 """ Check if an the object exists in the database. """
1021 return (len(self._session.execute("query \"name='%s' and version='%s' and type='%s' and instance='%s'\" -u -f \"%%objectname\"" % (self.name, self.version, self.type, self.instance), ObjectListResult(self._session)).output) == 1)
1022
1024 project = ""
1025 if self.type == 'project':
1026 project = "-p"
1027 if value.endswith("\\"):
1028 value += "\\"
1029 result = self._session.execute("attribute -modify \"%s\" -v \"%s\" %s \"%s\"" % (name, value, project, self))
1030 if result.status != 0 and result.status != None:
1031 raise CCMException("Error modifying '%s' attribute. Result: '%s'" % (name, result.output), result)
1032
1034 """ Provides access to Synergy object attributes through the dictionary
1035 item interface.
1036
1037 """
1038 result = self._session.execute("query \"name='%s' and version='%s' and type='%s' and instance='%s'\" -u -f \"%%%s\"" % (self.name, self.version, self.type, self.instance, name))
1039 if result.status != 0 and result.status != None:
1040 raise CCMException("Error retreiving '%s' attribute. Result: '%s'" % (name, result.output), result)
1041 if result.output.strip() == "<void>":
1042 return None
1043 return result.output.strip()
1044
1046 if name in self.keys():
1047 raise CCMException("Attribute '%s' already exist." % (name))
1048 args = ""
1049 proj_arg = ""
1050 if value != None:
1051 args += " -value \"%s\"" % value
1052 if self.type == "project":
1053 proj_arg = "-p"
1054 result = self._session.execute("attribute -create \"%s\" -type \"%s\" %s %s \"%s\"" % (name, type_, args, proj_arg, self.objectname))
1055 if result.status != 0 and result.status != None:
1056 raise CCMException("Error creating '%s' attribute. Result: '%s'" % (name, result.output), result)
1057
1059 """ The list of supported Synergy attributes. """
1060 result = self._session.execute("attribute -la \"%s\"" % self, AttributeNameListResult(self._session))
1061 return result.output
1062
1064 result = self._session.execute("query \"is_predecessor_of('%s') and name='%s'and version='%s'and type='%s'and instance='%s'\" -u -f \"%%objectname\"" % (o, self.name, self.version, self.type, self.instance), ObjectListResult(self._session))
1065 if len(result.output):
1066 return True
1067 return False
1068
1070 result = self._session.execute("query \"is_predecessor_of('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
1071 return result.output
1072
1074 result = self._session.execute("query \"is_successor_of('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
1075 return result.output
1076
1086
1088 """ Fast implementation of the recursive is_predecessor_of method. """
1089 input_objects = [self]
1090 while len(input_objects) > 0:
1091 query = " or ".join(["has_predecessor('%s')" % x for x in input_objects])
1092 result = self._session.execute("query \"query\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
1093 for s in result.output:
1094 if s == o:
1095 return True
1096 return False
1097
1099 result = self._session.execute("query \"has_successor('%s')\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
1100 for s in result.output:
1101 if s == o:
1102 return True
1103 for s in result.output:
1104 if s.is_recursive_sucessor_of(o):
1105 return True
1106 return False
1107
1109 """ Fast implementation of the recursive is_successor_of method. """
1110 input_objects = [self]
1111 while len(input_objects) > 0:
1112 query = " or ".join(["has_successor('%s')" % x for x in input_objects])
1113 result = self._session.execute("query \"query\" -u -f \"%%objectname\"" % query, ObjectListResult(self._session))
1114 for s in result.output:
1115 if s == o:
1116 return True
1117 return False
1118
1119 - def relate(self, ccm_object):
1120 result = self._session.execute("relate -name successor -from \"%s\" -to \"%s\"" % self, ccm_object, Result(self._session))
1121 if result.status != None and result.status != 0:
1122 raise CCMException("Error relating objects %s to %s\n%s" % (self, ccm_object, result.output))
1123
1125 """ Tries to find where an object is used. """
1126 result = self._session.execute("finduse \"%s\"" % self, FinduseResult(self))
1127 return result.output
1128
1129
1130 -class File(CCMObject):
1131 """ Wrapper for any Synergy file object """
1132
1135
1136 - def content(self):
1137 result = self._session.execute("cat \"%s\"" % self)
1138 return result.output
1139
1141 if os.path.exists(path):
1142 _logger.error("Error file %s already exists" % path)
1143 if not os.path.exists(os.path.dirname(path)):
1144 os.makedirs(os.path.dirname(path))
1145
1146 result = self._session.execute("cat \"%s\" > \"%s\"" % (self, os.path.normpath(path)))
1147 if result.status != 0 and result.status != None:
1148 raise CCMException("Error retrieving content from object %s in %s (error status: %s)\n%s" % (self, path, result.status, result.output), result)
1149
1150 - def merge(self, ccm_object, task):
1151 assert(ccm_object != None, "object must be defined.")
1152 assert(task != None, "task must be defined.")
1153 assert(task.type == "task", "task parameter must be of 'task' type.")
1154 result = self._session.execute("merge -task %s \"%s\" \"%s\"" % (task['displayname'], self, ccm_object))
1155
1156 validity = 0
1157 for line in result.output.splitlines():
1158 if re.match(r"Merge Source completed successfully\.", line):
1159 validity = 2
1160 elif re.match(r"Warning: Merge Source warning. \(overlaps during merge\)\.", line):
1161 validity = 1
1162 else:
1163 result = re.match(r"Associated object\s+(?P<object>.+)\s+with task", line)
1164 if result != None:
1165 return (self._session.create(result.groupdict()['object']), validity)
1166
1167 raise CCMException("Error during merge operation.\n" + result.output, result)
1168
1169 - def checkin(self, state, comment=None):
1170 if comment != None:
1171 comment = "-c \"%s\"" % comment
1172 else:
1173 comment = "-nc"
1174 result = self._session.execute("checkin -s \"%s\" %s \"%s\" " % (state, comment, self))
1175 for line in result.output.splitlines():
1176 _logger.debug(line)
1177 _logger.debug(r"Checked\s+in\s+'.+'\s+to\s+'%s'" % state)
1178 if re.match(r"Checked\s+in\s+'.+'\s+to\s+'%s'" % state, line) != None:
1179 return
1180 raise CCMException("Error checking in object %s,\n%s" % (self, result.output), result)
1181
1182
1184 """ Wrapper class for Synergy project object. """
1185
1190
1192 result = self._session.execute("rp -show tasks \"%s\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
1193 return result.output
1194
1196 """ Add a task to the update properties. """
1197 result = self._session.execute("up -add -task %s \"%s\"" % (task['displayname'], self.objectname))
1198 if result.status != None and result.status != 0:
1199 raise CCMException("Error adding task %s to project '%s'\n%s" % (task, self, result.output))
1200
1202 """ Remove a task to the update properties. """
1203 result = self._session.execute("up -remove -task %s \"%s\"" % (task['displayname'], self.objectname))
1204 if result.status != None and result.status != 0:
1205 raise CCMException("Error removing task %s from project '%s'\n%s" % (task, self, result.output))
1206
1208 """ Add a folder to the update properties. """
1209 result = self._session.execute("up -add -folder %s \"%s\"" % (folder['displayname'], self.objectname))
1210 if result.status != None and result.status != 0:
1211 raise CCMException("Error adding folder %s to project '%s'\n%s" % (folder, self, result.output))
1212
1214 """ Remove a folder to the update properties. """
1215 result = self._session.execute("up -remove -folder %s \"%s\"" % (folder['displayname'], self.objectname))
1216 if result.status != None and result.status != 0:
1217 raise CCMException("Error removing folder %s to project '%s'\n%s" % (folder, self, result.output))
1218
1220 """ Wrapper method to return the folder list from the update properties - please use the folders attribute to access it. """
1221 result = self._session.execute("up -show folders \"%s\" -u -f \"%%objectname\"" % self, ObjectListResult(self._session))
1222 return result.output
1223
1225 """ Wrapper method to return the subprojects list - please use the subprojects attribute to access it. """
1226 result = self._session.execute("query -t project \"recursive_is_member_of('%s', none)\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
1227 return result.output
1228
1237
1239 """ Get the release of the current object. Returns a Releasedef object. """
1240 self._release = Releasedef(self._session, self['release'])
1241 return self._release
1242
1244 """ Set the release of the current object. """
1245 self['release'] = release['displayname']
1246
1251
1253 """ Get the baseline of the current project. """
1254 if self._baseline == None:
1255 result = self._session.execute("up -show baseline_project \"%s\" -f \"%%displayname\" -u" % self.objectname)
1256 if result.output.strip().endswith('does not have a baseline project.'):
1257 return None
1258 self._baseline = self._session.create(result.output)
1259 _logger.debug('baseline: %s' % self._baseline)
1260 return self._baseline
1261
1263 """ Set project baseline. raise a CCMException in case or error. """
1264 args = ""
1265 if recurse:
1266 args += " -r"
1267 self._baseline = None
1268 result = self._session.execute("up -mb \"%s\" %s \"%s\"" % (baseline, args, self.objectname))
1269 if result.status != None and result.status != 0:
1270 raise CCMException("Error setting basline of project '%s'\n%s" % (self.objectname, result.output))
1271
1273 """ Set the update method for the project (and subproject if recurse is True). """
1274 assert name != None, "name must not be None."
1275 assert len(name) > 0, "name must not be an empty string."
1276 args = "-ru %s" % name
1277 if recurse:
1278 args += " -r"
1279 result = self._session.execute("up %s \"%s\"" % (args, self))
1280 if result.status != None and result.status != 0:
1281 raise CCMException("Error setting reconfigure properties to %s for project '%s'\nStatus: %s\n%s" % (name, self.objectname, result.status, result.output))
1282
1284 """ Apply update properties to subprojects. """
1285 args = ""
1286 if not baseline:
1287 args += "-no_baseline"
1288 if not tasks_and_folders:
1289 args += " -no_tasks_and_folders"
1290 if recurse:
1291 args += " -apply_to_subprojs"
1292 result = self._session.execute("rp %s \"%s\"" % (args, self.objectname))
1293 if result.status != None and result.status != 0:
1294 raise CCMException("Error applying update properties to subprojects for '%s'\n%s" % (self.objectname, result.output))
1295
1297 """ Return the directory attached to a project. """
1298 result = self._session.execute("query \"is_child_of('%s','%s')\" -u -f \"%%objectname\"" % (self.objectname, self.objectname), ObjectListResult(self._session))
1299 return result.output[0]
1300
1301 - def snapshot(self, targetdir, recursive=False):
1302 """ Take a snapshot of the project. """
1303 assert(targetdir != None, "targetdir must be defined.")
1304 if recursive:
1305 recursive = "-recurse"
1306 else:
1307 recursive = ""
1308 result = self._session.execute("wa_snapshot -path \"%s\" %s \"%s\"" % (os.path.normpath(targetdir), recursive, self.objectname))
1309 for line in result.output.splitlines():
1310 if re.match(r"^Creation of snapshot work area complete.|Copying to file system complete\.\s*$", line):
1311 return result.output
1312 raise CCMException("Error creation snapshot of %s,\n%s" % (self.objectname, result.output), result)
1313
1314 - def checkout(self, release, version=None, purpose=None, subprojects=True):
1315 """ Create a checkout of this project.
1316
1317 This will only checkout the project in Synergy. It does not create a work area.
1318
1319 :param release: The Synergy release tag to use.
1320 :param version: The new version to use for the project. This is applied to all subprojects.
1321 :param purpose: The purpose of the checkout. Determines automatically the role from the purpose
1322 and switch it automatically (Could be any role from the DB).
1323 """
1324 assert release != None, "Release object must be defined."
1325 if not release.exists():
1326 raise CCMException("Release '%s' must exist in the database." % release)
1327
1328 args = ''
1329 if version != None:
1330 args += '-to "%s"' % version
1331 role = None
1332 if purpose:
1333 self._session.role = get_role_for_purpose(self._session, purpose)
1334 args += " -purpose \"%s\"" % purpose
1335 if subprojects:
1336 args += " -subprojects"
1337 result = self._session.execute("checkout -project \"%s\" -release \"%s\" -no_wa %s" \
1338 % (self, release['displayname'], args), ProjectCheckoutResult(self._session, self.objectname))
1339 if role:
1340 self._session.role = role
1341 if result.project == None:
1342 raise CCMException("Error checking out project %s,\n%s" % (self.objectname, result.output), result)
1343 return result
1344
1345 - def work_area(self, maintain, recursive=None, relative=None, path=None, pst=None, wat=False):
1346 """ Configure the work area. This allow to enable it or disable it, set the path, recursion... """
1347 args = ""
1348 if maintain:
1349 args += "-wa"
1350 else:
1351 args += "-nwa"
1352
1353 if path != None:
1354 args += " -path \"%s\"" % path
1355
1356 if pst != None:
1357 args += " -pst \"%s\"" % pst
1358
1359 if relative != None and relative:
1360 args += " -relative"
1361 elif relative != None and not relative:
1362 args += " -not_relative"
1363
1364 if recursive != None and recursive:
1365 args += " -recurse"
1366 elif recursive != None and not recursive:
1367 args += " -no_recurse"
1368
1369 if wat:
1370 args += " -wat"
1371 result = self._session.execute("work_area -project \"%s\" %s" \
1372 % (self.objectname, args), Result(self._session))
1373 return result.output
1374
1375 - def update(self, recurse=True, replaceprojects=True, keepgoing=False, result=None):
1376 """ Update the project based on its reconfigure properties. """
1377 args = ""
1378 if recurse:
1379 args += " -r "
1380 if replaceprojects:
1381 args += " -rs "
1382 else:
1383 args += " -ks "
1384 if result == None:
1385 result = UpdateResult(self._session)
1386 result = self._session.execute("update %s -project %s" % (args, self.objectname), result)
1387 if not result.successful and not keepgoing:
1388 raise CCMException("Error updating %s" % (self.objectname), result)
1389 return result
1390
1391 - def reconcile(self, updatewa=True, recurse=True, consideruncontrolled=True, missingwafile=True, report=True):
1392 """ Reconcile the project to force the work area to match the database. """
1393 args = ""
1394 if updatewa:
1395 args += " -update_wa "
1396 if recurse:
1397 args += " -recurse "
1398 if consideruncontrolled:
1399 args += " -consider_uncontrolled "
1400 if missingwafile:
1401 args += " -missing_wa_file "
1402 if report:
1403 args += " -report reconcile.txt "
1404 result = self._session.execute("reconcile %s -project %s" % (args, self.objectname), Result(self._session))
1405 if re.search(r"There are no conflicts in the Work Area", result.output) == None and re.search(r"Reconcile completed", result.output) == None:
1406 raise CCMException("Error reconciling %s,\n%s" % (self.objectname, result.output), result)
1407 return result.output
1408
1410 result = self._session.execute("query -n %s -t project -f \"%%displayname\" -s %s -u -ns \"version smatch'%s'\"" % (self.name, state, filterstring))
1411 lines = result.output.splitlines()
1412 return lines[-1]
1413
1414 - def create_baseline(self, baseline_name, release, baseline_tag, purpose="System Testing", state="published_baseline"):
1415 result = self._session.execute("baseline -create %s -release %s -purpose \"%s\" -vt %s -project \"%s\" -state \"%s\"" % (baseline_name, release, purpose, baseline_tag, self.objectname, state))
1416 return result.output
1417
1418 - def sync(self, recurse=False, static=False):
1419 """ Synchronize project content. By default it is not been done recusively. (Not unittested)"""
1420 args = ""
1421 if recurse:
1422 args += " -recurse"
1423 if static:
1424 args += " -static"
1425 result = self._session.execute("sync %s -project \"%s\"" % (args, self.objectname))
1426 if result.status != None and result.status != 0:
1427 raise CCMException("Error during synchronization of %s: %s." % (self.objectname, result.output))
1428 return result.output
1429
1430 - def conflicts(self, recurse=False, tasks=False):
1431 args = "-noformat "
1432 if recurse:
1433 args += " -r"
1434 if tasks:
1435 args += " -t"
1436
1437 result = self._session.execute("conflicts %s \"%s\"" % (args, self.objectname), ConflictsResult(self._session))
1438 if result.status != None and result.status != 0:
1439 raise CCMException("Error during conflict detection of %s: %s." % (self.objectname, result))
1440 return result
1441
1442 tasks = property(_gettasks)
1443 folders = property(_getfolders)
1444 subprojects = property(_getsubprojects)
1445 release = property(_getrelease, _setrelease)
1446 baseline = property(_getbaseline, set_baseline)
1447
1448
1449 -class Dir(CCMObject):
1450 """ Wrapper class for Synergy dir object """
1451
1454
1459
1460
1462 """ Wrapper class for Synergy releasedef object """
1463
1466
1469
1470 component = property(_getcomponent)
1471
1472
1474 """ Wrapper class for Synergy folder object """
1475
1478
1480 """ Accessor for 'tasks' property. """
1481 result = self._session.execute("folder -show tasks \"%s\" -u -f \"%%objectname\"" % self.objectname, ObjectListResult(self._session))
1482 return result.output
1483
1487
1489 """ Get the mode used by the folder. """
1490 result = self._session.execute("folder -show mode \"%s\"" % self.objectname)
1491 return result.output.strip()
1492
1494 """ Get the query that populate the folder. """
1495 if self.mode.lower() == "query":
1496 result = self._session.execute("folder -show query \"%s\"" % self.objectname)
1497 return result.output.strip()
1498 else:
1499 raise CCMException("%s is not a query base folder." % (self.objectname))
1500
1502 """ Get the description associated with the folder. """
1503 r = self._session.execute("query -t folder -n %s -i %s -u -f \"%%description\"" % (self.name, self.instance))
1504 return r.output.strip()
1505
1511
1513 result = self._session.execute("folder -m -update -f \"%%objectname\"" % self.objectname)
1514 if result.status != None and result.status != 0:
1515 raise CCMException("Error updating the folder content %s: %s." % (self.objectname, result.output))
1516
1518 """ Associate an object to a task """
1519 class AddTaskException(CCMException):
1520 def __init__(self, reason, task, result):
1521 CCMException.__init__(self, reason, result)
1522 self.task = task
1523
1524 result = self._session.execute("folder -m -at \"%s\" \"%s\"" % (task.objectname, self.objectname))
1525 if re.search(r"(Added 1 task to)|(is already in folder)", result.output, re.M) is None:
1526 raise AddTaskException(result.output, result, task)
1527
1528 - def copy(self, existing_folder):
1529 """ Copy the contents of existing_folder into this folder.
1530
1531 This appends to the destination folder by default.
1532
1533 :param existing_folder: The destination Folder object.
1534 """
1535 result = self._session.execute("folder -copy %s -existing %s -append" % (self.objectname, existing_folder), FolderCopyResult(self._session))
1536 return result.output
1537
1538 objects = property(_getobjects)
1539 tasks = property(_gettasks)
1540 mode = property(_getmode)
1541 query = property(_getquery)
1542 is_query_based = property(lambda x: x.mode.lower() == "query")
1543 description = property(_getdescription)
1544
1545
1546 -class Task(CCMObject):
1547 """ Wrapper class for Synergy task object """
1548
1552
1556
1557 - def append(self, ccm_object):
1558 """ Associate an object to a task """
1559 class AddObjectException(CCMException):
1560 def __init__(self, comment, ccm_object):
1561 CCMException.__init__(self, comment)
1562 self.ccm_object = ccm_object
1563
1564 result = self._session.execute("task -associate \"%s\" -object \"%s\"" % (self.objectname, ccm_object.objectname))
1565 if not re.match(r"Associated object .+ with task .*\.", result.output, re.M):
1566 raise AddObjectException(result.output)
1567
1569 result = self._session.execute("task -modify \"%s\" -resolver %s" % (self.objectname, username))
1570 if not re.match(r"Changed resolver of task", result.output, re.M):
1571 raise CCMException("Error assigning task to user '%s',\n%s" % (username, result.output), result)
1572
1574 return self['task_synopsis']
1575
1576 @staticmethod
1577 - def create(session, release_tag, synopsis=""):
1578 assert release_tag.type == "releasedef", "release_tag must be a CCM object wrapper of releasedef type"
1579 result = session.execute("task -create -synopsis \"%s\" -release \"%s\"" % (synopsis, release_tag['displayname']), CreateNewTaskResult(session))
1580 return result.output
1581
1582 objects = property(_getobjects)
1583
1585
1586 if self.__unicode_str_text == None:
1587 self.__unicode_str_text = u'%s: %s' % (self['displayname'], self['task_synopsis'])
1588 return self.__unicode_str_text
1589
1591 return self.__unicode__().encode('ascii', 'replace')
1592
1594 """ Get task release. Use release property!"""
1595 result = self._session.execute("attribute -show release \"%s\"" % (self.objectname), Result(self._session))
1596 return result.output
1597
1599 """ Set task release. Use release property!"""
1600 result = self._session.execute("attribute -modify release -value \"%s\" \"%s\"" % (release_tag, self.objectname), Result(self._session))
1601 return result.output
1602
1603 release = property(get_release_tag, set_release_tag)
1604
1606 """ Allow to access Update Template property using Release and Purpose. """
1607 - def __init__(self, releasedef, purpose):
1608 assert(releasedef != None)
1609 assert(purpose != None)
1610 self._releasedef = releasedef
1611 self._purpose = purpose
1612
1614 """ Return the objectname representing this virtual object. """
1615 return "%s:%s" % (self._releasedef['displayname'], self._purpose)
1616
1622
1628
1630 """ The current Baseline selection mode """
1631 result = self._releasedef.session.execute("ut -sh bsm \"%s\"" % self.objectname())
1632 print result.output.strip()
1633 return result.output.strip()
1634
1635
1637 """ Read data from a ccmwaid file. This method is an helper to retreive a project from a physical location. """
1638 ccmwaid = open(filename, 'r')
1639
1640 dbpath = os.path.dirname(ccmwaid.readline().strip())
1641 database = os.path.basename(dbpath)
1642
1643 ccmwaid.readline().strip()
1644
1645 objectref = ccmwaid.readline().strip()
1646 ccmwaid.close()
1647 return {'dbpath': dbpath, 'database': database, 'objectname': objectref}
1648
1650 """ Uses the (_|.)ccmwaid.inf file to create a Project object. """
1651 ccmwaid = ".ccmwaid.inf"
1652 if os.name == 'nt':
1653 ccmwaid = "_ccmwaid.inf"
1654
1655 if (not os.path.exists(path + "/" + ccmwaid)):
1656 return None
1657 result = read_ccmwaid_info(path + "/" + ccmwaid)
1658
1659 return session.create(result['objectname'])
1660
1661
1662 -def open_session(username=None, password=None, engine=None, dbpath=None, database=None, reuse=True):
1663 """Provides a Session object.
1664
1665 Attempts to return a Session, based either on existing Synergy
1666 sessions or by creating a new one.
1667
1668 - If a .netrc file can be found on the user's personal drive,
1669 that will be read to obtain Synergy login information if it
1670 is defined there. This will be used to fill in any missing
1671 parameters not passed in the call to open_session().
1672
1673 The format of the .netrc file entries should be:
1674
1675 machine synergy login USERNAME password foobar account DATABASE_PATH@SERVER
1676
1677 If the details refer to a specific database, the machine can be the database name,
1678 instead of "synergy".
1679 - If an existing session is running that matches the supplied
1680 parameters, it will reuse that.
1681
1682 """
1683
1684 if CCM_BIN == None:
1685 raise CCMException("Could not find CM/Synergy executable in the path.")
1686 if password == None or username == None or engine == None or dbpath == None:
1687 if sys.platform == "win32":
1688 os.environ['HOME'] = "H:" + os.sep
1689 _logger.debug('Opening .netrc file')
1690 try:
1691 netrc_file = netrc.netrc()
1692 netrc_info = None
1693
1694 if database != None:
1695 netrc_info = netrc_file.authenticators(database)
1696
1697
1698 if netrc_info == None:
1699 netrc_info = netrc_file.authenticators('synergy')
1700
1701 if netrc_info != None:
1702 (n_username, n_account, n_password) = netrc_info
1703 if username == None:
1704 username = n_username
1705 if password == None:
1706 password = n_password
1707 if n_account != None:
1708 (n_dbpath, n_engine) = n_account.split('@')
1709 if dbpath == None and n_dbpath is not None:
1710 _logger.info('Database path set using .netrc (%s)' % n_dbpath)
1711 dbpath = n_dbpath
1712 if engine == None and n_engine is not None:
1713 _logger.info('Database engine set using .netrc (%s)' % n_engine)
1714 engine = n_engine
1715 except IOError:
1716 _logger.debug('Error accessing .netrc file')
1717
1718
1719 if username == None:
1720 username = os.environ['USERNAME']
1721
1722
1723 if dbpath == None and database != None:
1724 _logger.info('Database path set using the GSCM database.')
1725 dbpath = nokia.gscm.get_db_path(database)
1726
1727
1728 if engine == None and database != None:
1729 _logger.info('Database engine set using the GSCM database.')
1730 engine = nokia.gscm.get_engine_host(database)
1731
1732 _sessions = []
1733
1734 if password == None and reuse:
1735 _logger.debug('Querying for existing Synergy sessions')
1736 command = "%s status" % (CCM_BIN)
1737 pipe = os.popen(command, 'r')
1738 result = pipe.read()
1739 pipe.close()
1740 _logger.debug('ccm status result: ' + result)
1741 for match in re.finditer(r'(?P<ccmaddr>\w+:\d+:\d+.\d+.\d+.\d+(:\d+.\d+.\d+.\d+)?)(?P<current_session>\s+\(current\s+session\))?\nDatabase:\s*(?P<dbpath>\S+)', result, re.M):
1742 d = match.groupdict()
1743 _logger.debug(d['ccmaddr'])
1744 _logger.debug(os.environ['COMPUTERNAME'])
1745 _logger.debug(d['current_session'])
1746 if d['ccmaddr'].lower().startswith(os.environ['COMPUTERNAME'].lower()):
1747
1748
1749 existing_session = Session(username, engine, d['dbpath'], d['ccmaddr'], close_on_exit=False)
1750 _logger.debug('Existing session found: %s' % existing_session)
1751 _sessions.append(existing_session)
1752
1753 for session in _sessions:
1754 if session.dbpath == dbpath:
1755 return session
1756 else:
1757
1758 router_address = None
1759 if database == None and dbpath != None:
1760 database = os.path.basename(dbpath)
1761
1762 lock = fileutils.Lock(CCM_SESSION_LOCK)
1763 try:
1764 lock.lock(wait=True)
1765
1766 if database != None:
1767 _logger.info('Getting router address.')
1768 router_address = nokia.gscm.get_router_address(database)
1769 if sys.platform == "win32" and router_address != None:
1770 routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), 'r')
1771 current_router = routerfile.read().strip()
1772 routerfile.close()
1773 if current_router != router_address.strip():
1774 _logger.info('Updating %s' % (os.path.normpath(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"))))
1775 routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), "w+")
1776 routerfile.write("%s\n" % router_address)
1777 routerfile.close()
1778
1779
1780 _logger.info('Opening session.')
1781 new_session = Session.start(username, password, engine, dbpath)
1782 lock.unlock()
1783 return new_session
1784 finally:
1785 lock.unlock()
1786 raise CCMException("Cannot open session for user '%s'" % username)
1787
1788
1790 """ return role needed to modify project with checkout for purpose. """
1791 purposes = session.purposes()
1792 if purpose in purposes:
1793 if purposes[purpose]['status'] == 'prep':
1794 return 'build_mgr'
1795 else:
1796 raise CCMException("Could not find purpose '%s' in the database.\n Valid purpose are: %s." % (purpose, ','.join(purposes.keys())))
1797 return 'developer'
1798
1800 """ return role needed to modify project with a specific status. """
1801 if status == 'prep':
1802 return 'build_mgr'
1803 elif status == 'working':
1804 return 'developer'
1805 else:
1806 raise CCMException("Unknow status '%s'" % status)
1807
1809 """ Return the list of synergy session currently available on the local machine.
1810 If database is given then it tries to update the router address.
1811 """
1812 _logger.debug('Querying for existing Synergy sessions')
1813 if CCM_BIN == None:
1814 raise CCMException("Could not find CM/Synergy executable in the path.")
1815 command = "%s status" % (CCM_BIN)
1816
1817 lock = fileutils.Lock(CCM_SESSION_LOCK)
1818 result = ""
1819 output = []
1820 try:
1821
1822 if database != None:
1823 lock.lock(wait=True)
1824 _logger.info('Updating router address.')
1825 router_address = nokia.gscm.get_router_address(database)
1826 if sys.platform == "win32" and router_address != None:
1827 routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), 'r')
1828 current_router = routerfile.read().strip()
1829 routerfile.close()
1830 if current_router != router_address.strip():
1831 _logger.info('Updating %s' % (os.path.normpath(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"))))
1832 routerfile = open(os.path.join(os.path.dirname(CCM_BIN), "../etc/_router.adr"), "w+")
1833 routerfile.write("%s\n" % router_address)
1834 routerfile.close()
1835
1836 _logger.debug('Command: ' + command)
1837 (result, status) = _execute(command)
1838 if database != None:
1839 lock.unlock()
1840 if (status != 0):
1841 raise CCMException("Ccm status execution returned an error.")
1842 _logger.debug('ccm status result: ' + result)
1843 for match in re.finditer(r'Command Interface\s+@\s+(?P<ccmaddr>\w+:\d+:\d+.\d+.\d+.\d+(:\d+.\d+.\d+.\d+)*)(?P<current_session>\s+\(current\s+session\))?\s+Database:\s*(?P<dbpath>\S+)', result, re.M):
1844 data = match.groupdict()
1845 _logger.debug(data['ccmaddr'])
1846 _logger.debug(os.environ['COMPUTERNAME'])
1847 _logger.debug(data['current_session'])
1848 if data['ccmaddr'].lower().startswith(os.environ['COMPUTERNAME'].lower()):
1849
1850
1851 existing_session = Session(None, None, data['dbpath'], data['ccmaddr'], close_on_exit=False)
1852 _logger.debug('Existing session found: %s' % existing_session)
1853 output.append(existing_session)
1854 finally:
1855 if database != None:
1856 lock.unlock()
1857 return output
1858
1865
1866
1867
1868 CCM_BIN = fileutils.which("ccm")
1869 if sys.platform == "win32":
1870 CCM_BIN = fileutils.which("ccm.exe")
1871