ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/ozone/mgar/pkg/py311/gar/bin/uwatch
Revision: 11
Committed: Sat Jun 20 21:40:57 2026 UTC (4 weeks, 2 days ago) by operz
File size: 74893 byte(s)
Log Message:
Add Python 3.11.15 recipe (OZSWpy311); update readline to link against ncurses (for Python curses module)

File Contents

# Content
1 #!/usr/bin/python2.6
2
3 # TODO : check arguments for valid date
4 # TODO : check arguments for emtpy strings
5
6 #
7 # The contents of this file are subject to the COMMON DEVELOPMENT AND
8 # DISTRIBUTION LICENSE (CDDL) (the "License"); you may not use this
9 # file except in compliance with the License.
10 #
11 # Software distributed under the License is distributed on an "AS IS" basis,
12 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 # for the specific language governing rights and limitations under the
14 # License.
15 #
16 # Alternatively, the contents of this file may be used under the terms of
17 # either the GNU General Public License Version 3 or later (the "GPL"),
18 # in which case the provisions of the GPL are applicable instead
19 # of those above. If you wish to allow use of your version of this file only
20 # under the terms of either the GPL, and not to allow others to
21 # use your version of this file under the terms of the CDDL, indicate your
22 # decision by deleting the provisions above and replace them with the notice
23 # and other provisions required by the GPL. If you do not delete
24 # the provisions above, a recipient may use your version of this file under
25 # the terms of any one of the CDDL, or the GPL.
26 #
27 # Copyright 2010 OpenCSW (http://www.opencsw.org). All rights reserved.
28 # Use is subject to license terms.
29 #
30 #
31 # Contributors list :
32 #
33 # William Bonnet wbonnet@opencsw.org
34 #
35 #
36
37 # Import all the needed modules
38 import ConfigParser
39 import MySQLdb
40 import argparse
41 import datetime
42 import logging
43 import os
44 import pysvn
45 import re
46 import requests
47 import shutil
48 import subprocess
49 import sys
50
51 # ---------------------------------------------------------------------------------------------------------------------
52 #
53 #
54 class InvalidSourceDirectoryContentException(Exception):
55 """Exception raised when a method is called on the Abstract command class
56 """
57
58 # -----------------------------------------------------------------------------------------------------------------
59
60 def __init__(self, message):
61 self.message = message
62
63
64 # ---------------------------------------------------------------------------------------------------------------------
65 #
66 #
67 class AbstractCommandMethodCallException(Exception):
68 """Exception raised when a method is called on the Abstract command class
69 """
70
71 # -----------------------------------------------------------------------------------------------------------------
72
73 def __init__(self, message):
74 self.message = message
75
76 # ---------------------------------------------------------------------------------------------------------------------
77 #
78 #
79 class SvnClientException(Exception):
80 """Exception raised when an error occur while using svnClient
81 """
82
83 # -----------------------------------------------------------------------------------------------------------------
84
85 def __init__(self, message):
86 self.message = message
87
88 # ---------------------------------------------------------------------------------------------------------------------
89 #
90 #
91 class DatabaseConnectionException(Exception):
92 """Exception raised when an error occur while connecting to the database
93 """
94
95 # -----------------------------------------------------------------------------------------------------------------
96
97 def __init__(self, message):
98 self.message = message
99
100 # ---------------------------------------------------------------------------------------------------------------------
101 #
102 #
103 class DuplicatePackageException(Exception):
104 """Exception raised when an error occur while connecting to the database
105 """
106
107 # -----------------------------------------------------------------------------------------------------------------
108
109 def __init__(self, message):
110 self.message = message
111
112
113 # ---------------------------------------------------------------------------------------------------------------------
114 #
115 #
116 class UpstreamUrlRetrievalFailedException(Exception):
117 """Exception raised when an unsuported protocol is specified in the upstream url
118 """
119
120 # -----------------------------------------------------------------------------------------------------------------
121
122 def __init__(self, url):
123 self.url = url
124
125 # ---------------------------------------------------------------------------------------------------------------------
126 #
127 #
128 class NoUpstreamVersionFoundException(Exception):
129 """Exception raised when searching the upstream page content does not match the regexp
130 """
131
132 # -----------------------------------------------------------------------------------------------------------------
133
134 def __init__(self, url, regexp):
135 self.url = url
136 self.regexp = regexp
137
138
139 # ---------------------------------------------------------------------------------------------------------------------
140 #
141 #
142 class MissingArgumentException(Exception):
143 """Exception raised when a command line argument is missing
144 """
145
146 # -----------------------------------------------------------------------------------------------------------------
147
148 def __init__(self, value):
149 self.parameter = value
150
151 def __str__(self):
152 return repr(self.parameter)
153
154 # ---------------------------------------------------------------------------------------------------------------------
155 #
156 #
157 class InvalidArgumentException(Exception):
158 """Exception raised when a command line argument is invalid. For instance an invalid version or date
159 """
160
161 # -----------------------------------------------------------------------------------------------------------------
162
163 def __init__(self, value):
164 self.parameter = value
165
166 def __str__(self):
167 return repr(self.parameter)
168
169
170 # ---------------------------------------------------------------------------------------------------------------------
171
172 # ---------------------------------------------------------------------------------------------------------------------
173 #
174 #
175 class CommandLineParser(object):
176 """This class is used to parse command line. It process only options. Command verb is parsed by the main procedure
177 """
178
179 # -----------------------------------------------------------------------------------------------------------------
180
181 def __init__(self):
182 # Create the parser object
183 self.optionParser = argparse.ArgumentParser()
184
185 self.optionParser.add_argument(
186 "commands", help="Command to run, e.g. check-upstream", nargs="*")
187
188 # Add options to parser
189 self.optionParser.add_argument("--verbose", help="Activate verbose mode", action="store_true", dest="verbose")
190 self.optionParser.add_argument("--current-version", help="Current package version", action="store", dest="current_version")
191 self.optionParser.add_argument("--target-location", help="Target location. This is the directory in which the branch will be created (parent of the branch directory). Default value is ../branches", action="store", dest="target_location")
192 self.optionParser.add_argument("--regexp", help="Version matching regular expression", action="store", dest="regexp")
193 self.optionParser.add_argument("--source-directory", help="Source directory (place from where the build is copied). Default value is current directory", action="store", dest="source_directory")
194 self.optionParser.add_argument("--target-version", help="Package target version", action="store", dest="target_version")
195 self.optionParser.add_argument("--upstream-url", help="Upstream version page url", action="store", dest="upstream_url")
196
197 # Option used for reporting uwatch result
198 self.optionParser.add_argument("--uwatch-error", help="Flag used to report a uwatch error", action="store_true", dest="uwatch_error")
199 self.optionParser.add_argument("--uwatch-output", help="Flag used to report the uwatch output", action="store", dest="uwatch_output")
200 self.optionParser.add_argument("--uwatch-deactivated",help="Flag used to report a uwatch error", action="store_true", dest="uwatch_deactivated")
201 self.optionParser.add_argument("--uwatch-pkg-root", help="Defines the uwatch package root working directory", action="store", dest="uwatch_pkg_root")
202
203 # Option used for storing version information in the database
204 self.optionParser.add_argument("--gar-distfiles", help="Gar version of the package", action="store", dest="gar_distfiles")
205 self.optionParser.add_argument("--gar-version", help="Gar version of the package", action="store", dest="gar_version")
206 self.optionParser.add_argument("--upstream-version", help="Upstream version of the package", action="store", dest="upstream_version")
207 self.optionParser.add_argument("--gar-path", help="Relative path in svn repository", action="store", dest="gar_path")
208 self.optionParser.add_argument("--catalog-name", help="Catalog name", action="store", dest="catalog_name")
209 self.optionParser.add_argument("--package-name", help="Package name", action="store", dest="package_name")
210 self.optionParser.add_argument("--execution-date", help="Check date to be stored in the database", action="store", dest="execution_date")
211
212 self.optionParser.add_argument("--database-schema", help="Defines the database to use in the connection string", action="store", dest="database_schema")
213 self.optionParser.add_argument("--database-host", help="Defines the database host to use in the connection string", action="store", dest="database_host")
214 self.optionParser.add_argument("--database-user", help="Defines the database user to use in the connection string", action="store", dest="database_user")
215 self.optionParser.add_argument("--database-password", help="Defines the database password to use in the connection string", action="store", dest="database_password")
216
217 # -----------------------------------------------------------------------------------------------------------------
218
219 def parse(self):
220 self.args = self.optionParser.parse_args()
221 return self.args
222
223 # ---------------------------------------------------------------------------------------------------------------------
224 #
225 #
226 class UwatchConfiguration(object):
227 """This handles parameters retrieved either from command line or configuration file.
228 """
229
230 # -----------------------------------------------------------------------------------------------------------------
231
232 def initialize(self, args):
233 """Initialize configuration object. If configurations contains files reference, values from the files are
234 read and copied into this object. Next attempts to retrieve files stored information will read values from
235 this object.
236 """
237
238 # Default is no file parser
239 fileParser = None
240
241 # Test if the .uwatchrc file exists
242 if os.path.isfile( os.path.expanduser("~/.uwatchrc") ):
243 # Yes thus load values from the configuration file before processing args parameter
244 # This allow to override from the command line some values stored in the config file
245 fileParser = ConfigParser.SafeConfigParser()
246 fileParser.read( [os.path.expanduser("~/.uwatchrc") ] )
247
248 vars = {
249 "database-schema": None,
250 "database-host": None,
251 "database-user": None,
252 "database-password": None,
253 "uwatch-pkg-root": None,
254 }
255
256 # Read the database schema from the config file
257 self._database_schema = fileParser.get("main", "database-schema", vars=vars)
258
259 # Read the database hostname from the config file
260 self._database_host = fileParser.get("main", "database-host", vars=vars)
261
262 # Read the database user from the config file
263 self._database_user = fileParser.get("main", "database-user", vars=vars)
264
265 # Read the database password from the config file
266 self._database_password = fileParser.get("main", "database-password", vars=vars)
267
268 # Read the package root working dir from the config file
269 self._uwatch_pkg_root = fileParser.get("main", "uwatch-pkg-root", vars =vars)
270
271 # This member variable is a flag which defines the status of the verbose mode (True : activated)
272 self._verbose = args.verbose
273 logging_level = logging.DEBUG if self._verbose else logging.INFO
274 logging.basicConfig(level=logging_level)
275
276 # This member variable defines the value of the version of the package
277 # Current revision is not passed as a separated argument. It is part of the opencsw version number.
278 # Package version are defined as follow : version[,REV=revision]*
279 if args.current_version is not None:
280
281 # Parse the version string
282 ver = re.split(r"(?P<version>.*),REV=(?P<revision>.*)", args.current_version)
283
284 # Test if this is a match
285 if ver is None:
286 # No, thus raise an exception
287 msg = "Unable to parse %(version)s as a valid package version" % { 'version' : args.current_version }
288 raise InvalidArgumentException(msg)
289 else:
290 # If the length of array is one, the no revision is provided
291 if len(ver) == 1:
292 self._current_version = ver[0]
293 self._current_revision = ""
294 else:
295 self._current_version = ver[1]
296 self._current_revision = ver[2]
297
298 # Attempt to split again the string. If len is greater than 1
299 # Then there are at least two rev string => raise exception
300 ver = re.split(r"(?P<version>.*),REV=(?P<revision>.*)", self._current_version)
301 if len(ver) > 1:
302 msg = "Unable to parse %(version)s as a valid package version. There are more than one revision string" % { 'version' : args.current_version }
303 raise InvalidArgumentException(msg)
304
305 # This member variable defines the value of the regexp used to match the upstream web page
306 if args.regexp is not None:
307 self._regexp = args.regexp
308
309 # This member variable defines the url of the upstream web page used to check for new version
310 if args.upstream_url is not None:
311 self._upstream_url = args.upstream_url
312
313 # This member variable defines the target version of the package during upgrade
314 if args.target_version is not None:
315 self._target_version = args.target_version
316
317 # This member variable defines the target directory of the package during upgrade. This is the location of the new branch
318 if args.target_location is not None:
319 self._target_location = args.target_location
320
321 # This member variable defines the source directory of the package during upgrade. This is the location of the trunk (most of the time)
322 if args.source_directory is not None:
323 self._source_directory = args.source_directory
324
325 # This member variable defines the version of the package stored in the gar build description
326 if args.gar_version is not None:
327 self._gar_version = args.gar_version
328
329 # This member variable defines the upstream version of package
330 if args.upstream_version is not None:
331 self._upstream_version = args.upstream_version
332
333 # This member variable defines the relative path in the svn repository
334 if args.gar_path is not None:
335 self._gar_path = args.gar_path
336
337 # This member variable defines the catalog name of the package
338 if args.catalog_name is not None:
339 self._catalog_name = args.catalog_name
340
341 # This member variable defines the package name
342 if args.package_name is not None:
343 self._package_name = args.package_name
344
345 # This member variable defines the date of the execution (it is useful to be able to define a date to construct history of versions)
346 if args.execution_date is not None:
347 self._execution_date = args.execution_date
348
349 # This member variable defines the database to use in the connection string
350 if args.database_schema is not None:
351 self._database_schema = args.database_schema
352
353 # This member variable defines the database host to use in the connection string
354 if args.database_host is not None:
355 self._database_host = args.database_host
356
357 # This member variable defines the database user to use in the connection string
358 if args.database_user is not None:
359 self._database_user = args.database_user
360
361 # This member variable defines the database password to use in the connection string
362 if args.database_password is not None:
363 self._database_password = args.database_password
364
365 # This member variable defines the uwatch activation status
366 if args.uwatch_deactivated is not None:
367 self._uwatch_deactivated = True
368 else:
369 self._uwatch_deactivated = False
370
371 # This member variable defines the uwatch last report result
372 if args.uwatch_error is not None:
373 self._uwatch_error = True
374 else:
375 self._uwatch_error = False
376
377 # This member variable defines the uwatch last report result
378 if args.uwatch_pkg_root is not None:
379 self._uwatch_pkg_root = args.uwatch_pkg_root
380
381 # This member variable defines the uwatch last report output
382 if args.uwatch_output is not None:
383 self._uwatch_output = args.uwatch_output
384
385 # This member variable defines the gar distfiles
386 if args.gar_distfiles is not None:
387 self._gar_distfiles = args.gar_distfiles
388
389 # -----------------------------------------------------------------------------------------------------------------
390
391 def __init__(self):
392
393 # Initialize all variables to None. Even if useless, the purpose of this to keep up to date the list
394 self._verbose = None
395 self._uwatch_deactivated = False
396 self._uwatch_error = False
397 self._uwatch_pkg_root = None
398 self._current_version = None
399 self._current_revision = ""
400 self._regexp = None
401 self._upstream_url = None
402 self._target_version = None
403 self._source_directory = "."
404 self._target_location = "../branches"
405 self._gar_version = None
406 self._upstream_version = None
407 self._gar_path = None
408 self._catalog_name = None
409 self._package_name = None
410 self._execution_date = None
411 self._database_schema = None
412 self._database_host = None
413 self._database_user = None
414 self._database_password = None
415 self._gar_distfiles = None
416 self._uwatch_output = None
417
418 # -----------------------------------------------------------------------------------------------------------------
419
420 def getCurrentVersion(self):
421 return self._current_version
422
423 # -----------------------------------------------------------------------------------------------------------------
424
425 def getCurrentRevision(self):
426 return self._current_revision
427
428 # -----------------------------------------------------------------------------------------------------------------
429
430 def getRegexp(self):
431 # In case the regexp is not define, we try to guess it using the distfile
432 if (self._regexp is None) and (self._gar_distfiles is not None):
433 urg = UwatchRegexGenerator()
434 auto_regex_list = urg.GenerateRegex(self._catalog_name, self._gar_distfiles)
435 # There are many possible regexes, use the first one.
436 self._regexp = auto_regex_list[0]
437
438 return self._regexp
439
440 # -----------------------------------------------------------------------------------------------------------------
441
442 def getUpstreamURL(self):
443 return self._upstream_url
444
445 # -----------------------------------------------------------------------------------------------------------------
446
447 def getVerbose(self):
448 return self._verbose
449
450 # -----------------------------------------------------------------------------------------------------------------
451
452 def getSourceDirectory(self):
453 return self._source_directory
454
455 # -----------------------------------------------------------------------------------------------------------------
456
457 def getTargetLocation(self):
458 return self._target_location
459
460 # -----------------------------------------------------------------------------------------------------------------
461
462 def getTargetVersion(self):
463 return self._target_version
464
465 # -----------------------------------------------------------------------------------------------------------------
466
467 def getGarVersion(self):
468 return self._gar_version
469
470 # -----------------------------------------------------------------------------------------------------------------
471
472 def getUpstreamVersion(self):
473 return self._upstream_version
474
475 # -----------------------------------------------------------------------------------------------------------------
476
477 def getGarPath(self):
478 if self.getUwatchPkgRoot():
479 return self._gar_path.replace(self.getUwatchPkgRoot(), "")
480 else:
481 return self._gar_path
482
483 # -----------------------------------------------------------------------------------------------------------------
484
485 def getCatalogName(self):
486 return self._catalog_name
487
488 # -----------------------------------------------------------------------------------------------------------------
489
490 def getPackageName(self):
491 return self._package_name
492
493 # -----------------------------------------------------------------------------------------------------------------
494
495 def getExecutionDate(self):
496 return self._execution_date
497
498 # -----------------------------------------------------------------------------------------------------------------
499
500 def getDatabaseSchema(self):
501 return self._database_schema
502
503 # -----------------------------------------------------------------------------------------------------------------
504
505 def getDatabaseHost(self):
506 return self._database_host
507
508 # -----------------------------------------------------------------------------------------------------------------
509
510 def getDatabaseUser(self):
511 return self._database_user
512
513 # -----------------------------------------------------------------------------------------------------------------
514
515 def getDatabasePassword(self):
516 return self._database_password
517
518 # -----------------------------------------------------------------------------------------------------------------
519
520 def getUwatchError(self):
521 return self._uwatch_error
522
523 # -----------------------------------------------------------------------------------------------------------------
524
525 def getUwatchDeactivated(self):
526 return self._uwatch_deactivated
527
528 # -----------------------------------------------------------------------------------------------------------------
529
530 def getUwatchPkgRoot(self):
531 return self._uwatch_pkg_root
532
533 # -----------------------------------------------------------------------------------------------------------------
534
535 def getGarDistfiles(self):
536 return self._gar_distfiles
537
538 # -----------------------------------------------------------------------------------------------------------------
539
540 def getUwatchOutput(self):
541 return self._uwatch_output
542
543
544 # ---------------------------------------------------------------------------------------------------------------------
545 #
546 #
547 class AbstractCommand(object):
548 """Base class for command implementation. You should create a derived class per command to implemente.
549 A "command" represent a concrete action verb given to the program.
550 """
551
552 # -----------------------------------------------------------------------------------------------------------------
553
554 def __init__(self, name):
555 self.config = UwatchConfiguration()
556 self.name = name
557
558 # -----------------------------------------------------------------------------------------------------------------
559
560 def getName(self):
561 return self.name
562
563 # -----------------------------------------------------------------------------------------------------------------
564
565 def execute(self, arguments):
566 print "Internal error : Abstract command method called\n"
567 raise AbstractCommandMethodCallException("execute")
568
569 # ---------------------------------------------------------------------------------------------------------------------
570 #
571 #
572 class UpstreamWatchCommand(AbstractCommand):
573 """UpstreamWatch command. This command is the base class used for all upstream watch commands. It provides argument checking
574 url content retrieving, version comparison, etc.
575 """
576
577 # -----------------------------------------------------------------------------------------------------------------
578
579 def UrlContentRetrieve(self, url):
580
581 try:
582 # Request the upstream URL and open it
583 # req = Request(url)
584 # response = urlopen(req)
585 response = requests.get(url)
586 response.raise_for_status()
587
588 except (requests.exceptions.ConnectionError,
589 requests.exceptions.HTTPError) as e:
590 print e
591 raise UpstreamUrlRetrievalFailedException(' '.join((url, unicode(e))))
592 else:
593 # everything is fine, retrieve the page content
594 return response.text
595
596
597 # -----------------------------------------------------------------------------------------------------------------
598
599 def CompareVersionAndGetNewest(self, version1, version2):
600
601 # we consider the version to be composed of several elements separated by '.' ',' '-' or '_'
602 # an elements can be a string or a number
603 # at each step we extract the next elements of the two version strings and compare them
604
605 if not isinstance(version1, basestring):
606 print "Version is not a string. Please check environnement variable UFILES_REGEX"
607 print version1
608
609 if not isinstance(version2, basestring):
610 print "Version is not a string. Please check environnement variable UFILES_REGEX"
611 print version2
612
613 # Retrieve the tokens from both version. Use . - , and _ as splitters
614 splittingRegExp = "(?:[\.,_-])"
615 tokens1 = re.split(splittingRegExp, version1)
616 tokens2 = re.split(splittingRegExp, version2)
617
618 # Iterates the toeksn of version 1, pop tokens one by one and compare to the token at same
619 # in version 2
620 while len(tokens1) > 0:
621 # If we still have tokens in version 1 and version 2 is empty, then version 1 is newer
622 # TODO: may have to deal with beta suffixes...
623 if len(tokens2) == 0:
624 return version1
625
626 # Convert both elements to integer
627 # TODO : handles chars in versions
628 elem1 = tokens1.pop(0)
629 elem2 = tokens2.pop(0)
630
631 # If both elements are integer, then convert to int before comparison, otherwise compare strings
632 try:
633 elem1 = int(elem1)
634 elem2 = int(elem2)
635 except:
636 elem1 = str(elem1)
637 elem2 = str(elem2)
638 # print "Doing string comparison"
639
640 # print "Comparing %(elem1)s and %(elem2)s" % { 'elem1' : elem1 , 'elem2' : elem2 }
641
642 # if elements are the same continue the loop
643 if elem1 == elem2:
644 continue
645
646 # Test if elem1 is more recent
647 if elem1 > elem2:
648 # print "%(elem1)s > %(elem2)s" % { 'elem1' : elem1 , 'elem2' : elem2 }
649 return version1
650 else:
651 # print "%(elem1)s < %(elem2)s" % { 'elem1' : elem1 , 'elem2' : elem2 }
652 return version2
653
654 return version1
655
656 # ---------------------------------------------------------------------------------------------------------------------
657 #
658 #
659 class CheckUpstreamCommand(UpstreamWatchCommand):
660 """CheckUpstream command. This command retrieve the upstream web page and search for a new version. Version check is
661 done by matching the regexp from the makefile on the page. Results are sorted to get the highest available and
662 compared to current version
663 """
664
665 # -----------------------------------------------------------------------------------------------------------------
666
667 def __init__(self, name):
668 super(CheckUpstreamCommand, self).__init__(name)
669
670 # -----------------------------------------------------------------------------------------------------------------
671
672 def checkArgument(self):
673
674 # Variable used to flag that we have a missing argument
675 argsValid = True
676
677 # Current version is mandatory
678 if self.config.getCurrentVersion() is None:
679 print "Error : Current version is not defined. Please use --current-version flag, or --help to display help"
680 argsValid = False
681
682 # Regexp is mandatory
683 if self.config.getRegexp() is None:
684 print "Error : Regexp is not defined. Please use --regexp flag, or --help to display help"
685 argsValid = False
686
687 # UpstreamURL is mandatory
688 if self.config.getUpstreamURL() is None:
689 print "Error : Upstream version page URL is not defined. Please use --upstream-url flag, or --help to display help"
690 argsValid = False
691
692 # If arguments are not valid raise an exception
693 if not argsValid:
694 raise MissingArgumentException("Some mandatory arguments are missing. Unable to continue.")
695
696 # -----------------------------------------------------------------------------------------------------------------
697
698 def execute(self, args):
699
700 try:
701
702 # Initialize configuration
703 self.config.initialize(args)
704
705 # Need a way to check that all options needed are available
706 self.checkArgument()
707
708 url = self.config.getUpstreamURL()
709 version_finding_regex = self.config.getRegexp()
710
711 logging.info("Will use %r against %r", version_finding_regex, url)
712 content = self.UrlContentRetrieve(url)
713
714 # Search the strings matching the regexp passed through command line arguments
715 p = re.compile(version_finding_regex)
716 matches = p.findall(content)
717 logging.info("CheckUpstreamCommand.execute(): matches=%s",
718 repr(matches))
719
720 # Check if we have found some results
721 if len(matches) == 0:
722 raise NoUpstreamVersionFoundException(self.config.getUpstreamURL(), self.config.getRegexp())
723 else:
724 newestVersion = self.config.getCurrentVersion()
725 while len(matches) > 0:
726 newestVersion = self.CompareVersionAndGetNewest(newestVersion, matches.pop(0))
727
728 # At the end of the processing loop, test if we have a newer version avail, if yes output it
729 if newestVersion <> self.config.getCurrentVersion():
730 print newestVersion
731
732 # Exit after processing, eveythin gis ok, return true to the command processor
733 return True
734
735 except MissingArgumentException as instance:
736
737 # Display a cool error message :)
738 print instance.parameter
739
740 # Exits through exception handling, thus return false to the command processor
741 return False
742
743 except UpstreamUrlRetrievalFailedException as instance:
744
745 # Exits through exception handling, thus return false to the command processor
746 return False
747
748 except NoUpstreamVersionFoundException as instance:
749
750 # Exits through exception handling, thus return false to the command processor
751 return False
752
753
754 # ---------------------------------------------------------------------------------------------------------------------
755 #
756 #
757 class GetUpstreamLatestVersionCommand(UpstreamWatchCommand):
758 """GetUpstreamLatestVersion command. This command retrieve the upstream web page and search for the latest version.
759 Version check is done by matching the regexp from the makefile on the page. Results are sorted to get the newest version
760 """
761
762 # -----------------------------------------------------------------------------------------------------------------
763
764 def __init__(self, name):
765 super(GetUpstreamLatestVersionCommand, self).__init__(name)
766
767 # -----------------------------------------------------------------------------------------------------------------
768
769 def checkArgument(self):
770
771 # Variable used to flag that we have a missing argument
772 argsValid = True
773
774 # Regexp is mandatory
775 if self.config.getRegexp() is None:
776 print "Error : Regexp is not defined. Please use --regexp flag, or --help to display help"
777 argsValid = False
778
779 # UpstreamURL is mandatory
780 if self.config.getUpstreamURL() is None:
781 print "Error : Upstream version page URL is not defined. Please use --upstream-url flag, or --help to display help"
782 argsValid = False
783
784 # If arguments are not valid raise an exception
785 if not argsValid:
786 raise MissingArgumentException("Some mandatory arguments are missing. Unable to continue.")
787
788 # -----------------------------------------------------------------------------------------------------------------
789
790 def execute(self, args):
791
792 try:
793
794 # Initialize configuration
795 self.config.initialize(args)
796
797 # Need a way to check that all options needed are available
798 self.checkArgument()
799
800 # Call the method in charge of retrieving upstream content
801 content = self.UrlContentRetrieve(self.config.getUpstreamURL())
802
803 # Search the strings matching the regexp passed through command line arguments
804 regex_str = self.config.getRegexp()
805 p = re.compile(regex_str)
806 matches = p.findall(content)
807 logging.info("GetUpstreamLatestVersionCommand.execute(): "
808 "regex=%s",
809 repr(regex_str))
810 logging.info("GetUpstreamLatestVersionCommand.execute(): "
811 "matches=%s",
812 repr(matches))
813
814 # Check if we have found some results
815 if len(matches) == 0:
816 raise NoUpstreamVersionFoundException(self.config.getUpstreamURL(), self.config.getRegexp())
817 else:
818 newestVersion = matches.pop(0)
819 while len(matches) > 0:
820 newestVersion = self.CompareVersionAndGetNewest(newestVersion, matches.pop(0))
821
822 # At the end of the processing loop, we print the version
823 print newestVersion
824
825 # Exit after processing, eveythin gis ok, return true to the command processor
826 return True
827
828 except MissingArgumentException as instance:
829
830 # Display a cool error message :)
831 print instance.parameter
832
833 # Exits through exception handling, thus return false to the command processor
834 return False
835
836 except UpstreamUrlRetrievalFailedException as instance:
837
838 # Exits through exception handling, thus return false to the command processor
839 return False
840
841 except NoUpstreamVersionFoundException as instance:
842
843 # Exits through exception handling, thus return false to the command processor
844 return False
845
846 # ---------------------------------------------------------------------------------------------------------------------
847 #
848 #
849 class GetUpstreamVersionListCommand(UpstreamWatchCommand):
850 """GetUpstreamVersionList command. This command retrieve the upstream web page and search for all the versions.
851 Version check is done by matching the regexp from the makefile on the page.
852 """
853
854 # -----------------------------------------------------------------------------------------------------------------
855
856 def __init__(self, name):
857 super(GetUpstreamVersionListCommand, self).__init__(name)
858
859 # -----------------------------------------------------------------------------------------------------------------
860
861 def checkArgument(self):
862
863 # Variable used to flag that we have a missing argument
864 argsValid = True
865
866 # Regexp is mandatory
867 if self.config.getRegexp() is None:
868 print "Error : Regexp is not defined. Please use --regexp flag, or --help to display help"
869 argsValid = False
870
871 # UpstreamURL is mandatory
872 if self.config.getUpstreamURL() is None:
873 print "Error : Upstream version page URL is not defined. Please use --upstream-url flag, or --help to display help"
874 argsValid = False
875
876 # If arguments are not valid raise an exception
877 if not argsValid:
878 raise MissingArgumentException("Some mandatory arguments are missing. Unable to continue.")
879
880 # -----------------------------------------------------------------------------------------------------------------
881
882 def compareAndSortVersion(self, a, b):
883 """This function is a wrapper to the comparison function. CompareVersionAndGetNewest returns the string containing
884 the newest version of the two arguments. Since sort method used on list need to have an integer return value, this
885 wrapper do the call to CompareVersionAndGetNewest and returns an int
886 """
887
888 if self.CompareVersionAndGetNewest(a,b) == a:
889 return 1
890 else:
891 return -1
892
893 # -----------------------------------------------------------------------------------------------------------------
894
895 def execute(self, args):
896
897 try:
898
899 # Initialize configuration
900 self.config.initialize(args)
901
902 # Need a way to check that all options needed are available
903 self.checkArgument()
904
905 # Call the method in charge of retrieving upstream content
906 content = self.UrlContentRetrieve(self.config.getUpstreamURL())
907
908 listURL = self.config.getUpstreamURL().split(' ')
909
910 # Search the strings matching the regexp passed through command line arguments
911 p = re.compile(self.config.getRegexp())
912 matches = p.findall(content)
913
914 # Check if we have found some results
915 if len(matches) == 0:
916 raise NoUpstreamVersionFoundException(self.config.getUpstreamURL(), self.config.getRegexp())
917 else:
918 # Remove duplicated entries
919 myList = []
920 for version in matches:
921 myList.append(version)
922
923 l = list(set(myList))
924 l.sort(self.compareAndSortVersion)
925
926 # Print every version in the list
927 for version in l:
928 print version
929
930 # Exit after processing, eveythin gis ok, return true to the command processor
931 return True
932
933 except MissingArgumentException as instance:
934
935 # Display a cool error message :)
936 print instance.parameter
937
938 # Exits through exception handling, thus return false to the command processor
939 return False
940
941 except UpstreamUrlRetrievalFailedException as instance:
942
943 # Exits through exception handling, thus return false to the command processor
944 return False
945
946 except NoUpstreamVersionFoundException as instance:
947
948 # Exits through exception handling, thus return false to the command processor
949 return False
950
951
952 # ---------------------------------------------------------------------------------------------------------------------
953 #
954 #
955 class UpgradeToVersionCommand(UpstreamWatchCommand):
956 """UpgradeToVersion command. This command upgrade the build description from a version to another.
957 Current files in trunk are copied to a new branch. Branch is named accord to the following pattern :
958 PKG/branches/upgrade_from_CURRENTVERSION_to_DESTVERSION. After copy, version in the Makefile is modified.
959 An optional argument can be passed to commit after branch creation.
960
961 """
962
963 # -----------------------------------------------------------------------------------------------------------------
964
965 def __init__(self, name):
966 super(UpgradeToVersionCommand, self).__init__(name)
967
968 # -----------------------------------------------------------------------------------------------------------------
969
970 def checkArgument(self):
971
972 # Variable used to flag that we have a missing argument
973 argsValid = True
974
975 # FromVersion is mandatory
976 if self.config.getCurrentVersion() is None:
977 print "Error : Current version is not defined. Please use --current-version flag, or --help to display help"
978 argsValid = False
979
980 # ToVersion is mandatory
981 if self.config.getTargetVersion() is None:
982 print "Error : Target version is not defined. Please use --target-version flag, or --help to display help"
983 argsValid = False
984
985 # ToVersion is mandatory
986 if self.config.getTargetLocation() is None:
987 print "Error : Target directory is not defined. Please use --target-location flag, or --help to display help"
988 argsValid = False
989
990 # If arguments are not valid raise an exception
991 if not argsValid:
992 raise MissingArgumentException("Some mandatory arguments are missing. Unable to continue.")
993
994 # -----------------------------------------------------------------------------------------------------------------
995
996 def checkWorkingDirectory(self):
997 """ This method checks that the command is executed from a valid working directory. A valid working directory
998 is a directory in which we find a package buildDescription that means a Makefile and a gar directory or symlink
999 """
1000
1001 # Check that the Makefile exist
1002 if not os.path.isfile(self.config.getSourceDirectory() + "/Makefile"):
1003 # No it does not exist, thus generate an error message
1004 msg = "Error : there is no Makefile under %(src)s" % { "src" : os.path.abspath(self.config.getSourceDirectory()) }
1005
1006 # Then raise an exception
1007 raise InvalidSourceDirectoryContentException(msg)
1008
1009 # Check that the gar directory exists (can be a directory or symlink)
1010 if not os.path.isdir(self.config.getSourceDirectory() + "/gar"):
1011 # No it does not exist, thus generate an error message
1012 msg = "Error : there is no gar directory under %(src)s" % { "src" : os.path.abspath(self.config.getSourceDirectory()) }
1013
1014 # Then raise an exception
1015 raise InvalidSourceDirectoryContentException(msg)
1016
1017 # -----------------------------------------------------------------------------------------------------------------
1018
1019 def getGarRelativeTargetDirectory(self):
1020 """ This method return None if gar directory is an actual directory, or a relative path if gar is a symlink to
1021 a real directory. In case of a symlink pointing to another symlink, we do not try to get the absolute path
1022 having one level of indirection is enough.
1023 The target directory is a relative path. This path is adjusted to be consistent from the target directory. It
1024 has to be modified since it is basically a relative path from the source directory.
1025 """
1026
1027 # Get the newgar information
1028 garDir = self.config.getSourceDirectory() + "/gar"
1029 if os.path.islink(garDir):
1030 garTarget = os.path.relpath(os.path.abspath(os.readlink(garDir)), os.path.abspath(targetDir))
1031 else:
1032 garTarget = None
1033
1034 # -----------------------------------------------------------------------------------------------------------------
1035
1036 def getGarRelativeTargetDirectory(self):
1037 """ This method return None if gar directory is an actual directory, or a relative path if gar is a symlink to
1038 a real directory. In case of a symlink pointing to another symlink, we do not try to get the absolute path
1039 having one level of indirection is enough.
1040 The target directory is a relative path. This path is adjusted to be consistent from the target directory. It
1041 has to be modified since it is basically a relative path from the source directory.
1042 """
1043
1044 # Get the newgar information
1045 garDir = self.config.getSourceDirectory() + "/gar"
1046 if os.path.islink(garDir):
1047 garTarget = os.path.relpath(os.path.abspath(os.readlink(garDir)), os.path.abspath(self.getTargetDirectory()))
1048 else:
1049 garTarget = None
1050
1051 return garTarget
1052
1053 # -----------------------------------------------------------------------------------------------------------------
1054
1055 def getTargetDirectory(self):
1056 """ This method return the target directory which is a computed value based on target location, current version
1057 and target version
1058 """
1059
1060 return self.config.getTargetLocation() + "/upgrade_from_" + self.config.getCurrentVersion() + "_to_" + self.config.getTargetVersion()
1061
1062 # -----------------------------------------------------------------------------------------------------------------
1063
1064 def copySvnSourceToTarget(self, garRelativeTarget):
1065 """ This method copy sources from the working copy to the target in the same working copy. If garRelativeTarget is not
1066 None, it means gar directory is a symlink. Then once copy is done it is deleted in the target directory and
1067 recreated to point to the new relative directory
1068 """
1069
1070 try:
1071 # Create a new svn client
1072 svnClient = pysvn.Client()
1073
1074 # Do the actual copy in the svn working copy
1075 svnClient.copy(os.path.abspath(self.config.getSourceDirectory()), self.getTargetDirectory())
1076
1077 # Backup the current directory
1078 curDir = os.getcwd()
1079
1080 # Change to target directory
1081 os.chdir(self.getTargetDirectory())
1082
1083 # Test if gar relative path is defined
1084 if garRelativeTarget:
1085 # Test if gar directory is a symlink and
1086 if os.path.islink("./gar"):
1087 os.remove("./gar")
1088 os.symlink(garRelativeTarget, "./gar")
1089 # No ... :( This a "should not happen error" since it was a symlink before the copy. Exit
1090 else:
1091 print "Internal error : gar is not a symlink but garRelativeTarget is defined"
1092 return False
1093 # No else but ... If gar relative path is not defined, we have copied a real directory. Nothing to do in this case
1094
1095 # Restore the working directory
1096 os.chdir(curDir)
1097
1098 # SVN client exception handling
1099 except pysvn.ClientError , e:
1100 # Generate a cool error message
1101 msg = "SVN Client error : " + e.args[0] + "\n" + "Error occured when executing command svnClient.copy(%(src)s, %(dest)s)" \
1102 % { 'src' : os.path.abspath(self.config.getSourceDirectory()), 'dest' : self.getTargetDirectory() }
1103
1104 # Then raise the exception
1105 raise SvnClientException(msg)
1106
1107 # -----------------------------------------------------------------------------------------------------------------
1108
1109 def modifyVersion(self):
1110 """ This method modifies the version in the Makefile. It replaces current version by new version.
1111 Version has to be defined on a single line strting by VERSION, having some spaces or tabs then
1112 and egal sign = then some tabs or spaces and the version vaue to finish the line
1113 """
1114
1115 # Backup the current directory
1116 curDir = os.getcwd()
1117
1118 # Change to target directory
1119 os.chdir(self.getTargetDirectory())
1120
1121 # Array storing the Makefile lines
1122 lines = []
1123
1124 # Iterate each line in the file
1125 for line in open("./Makefile", 'r'):
1126 # Match the file line by line
1127 m = re.match(r"\s*VERSION\s*=\s*(?P<version>.*)", line)
1128
1129 # Test if this is a match
1130 if m is None:
1131 # No, thus output the current line without modifications
1132 lines.append(line)
1133 else:
1134 # Yes it is a match, thus output the modified line
1135 lines.append("VERSION = " + self.config.getTargetVersion() + "\n")
1136
1137 # Open the new Makefile for output
1138 f = open("./Makefile", 'w')
1139
1140 # Iterates the array of lines and write each one to the Makefile
1141 for element in lines:
1142 f.write(element)
1143
1144 # Close the Makefile
1145 f.close()
1146
1147 # Restore the working directory
1148 os.chdir(curDir)
1149
1150 # -----------------------------------------------------------------------------------------------------------------
1151
1152 def execute(self, args):
1153
1154 try:
1155
1156 # Initialize configuration
1157 self.config.initialize(args)
1158
1159 # Need a way to check that all options needed are available
1160 self.checkArgument()
1161
1162 # Check that the command is launched from a valid directory
1163 self.checkWorkingDirectory()
1164
1165 # Generates the target directory
1166 self.getTargetDirectory()
1167
1168 # Get the new gar information
1169 garRelativeTarget = self.getGarRelativeTargetDirectory()
1170
1171 # Copy target directory to destination
1172 self.copySvnSourceToTarget(garRelativeTarget)
1173
1174 # Modify the version inside the Makefile
1175 self.modifyVersion()
1176
1177 # Exit after processing, eveythin gis ok, return true to the command processor
1178 return True
1179
1180 # Handles exception that occurs when arguments are incorrect
1181 except MissingArgumentException, instance:
1182
1183 # Display a cool error message :)
1184 print instance.parameter
1185
1186 # Exits through exception handling, thus return false to the command processor
1187 return False
1188
1189 # Handles SVN client exception
1190 except SvnClientException , e:
1191
1192 # Display a cool error message :)
1193 print e.message
1194
1195 # Exits through exception handling, thus return false to the command processor
1196 return False
1197
1198 # Handles exceptions which might occur while checking source directory content
1199 except InvalidSourceDirectoryContentException , e:
1200
1201 # Display a cool error message :)
1202 print e.message
1203
1204 # Exits through exception handling, thus return false to the command processor
1205 return False
1206
1207 # ---------------------------------------------------------------------------------------------------------------------
1208 #
1209 #
1210 class ReportPackageVersionCommand(UpstreamWatchCommand):
1211 """ReportPackageVersion command. This command report and store in the database the values of version and date passed
1212 by arguments to upstream watch. Unique key is the composed by garpath and catalog name. It means the same package can
1213 lie into different path in the svn repository.
1214 """
1215
1216 # -----------------------------------------------------------------------------------------------------------------
1217
1218 def __init__(self, name):
1219 super(ReportPackageVersionCommand, self).__init__(name)
1220 self.conn = None
1221
1222 # -----------------------------------------------------------------------------------------------------------------
1223
1224 def openDatabaseConnection(self):
1225 """This method open a connection to the mysql database using value from the configuration parser. The result of
1226 connect method is stored into a connection object
1227 """
1228
1229 # Open the database connection
1230 try:
1231 self.conn = MySQLdb.connect(host = self.config.getDatabaseHost(),
1232 passwd = self.config.getDatabasePassword(),
1233 db = self.config.getDatabaseSchema(),
1234 user = self.config.getDatabaseUser() )
1235
1236 except MySQLdb.Error, e:
1237 msg = "Error %d: %s" % (e.args[0], e.args[1])
1238 raise DatabaseConnectionException(msg)
1239
1240 # Check that the object we got in return if defiend
1241 if self.conn is None:
1242 # No, raise a DatabaseConnectionException
1243 msg = "Unable to connect to database using host = %(host)s, db = %(db)s, user = %(user)s, passwd = %(passwd)% " % { "host" : self.config.getDatabaseHost(), "passwd" : self.config.getDatabasePassword(), "db" : self.config.getDatabaseSchema(), "user" : self.config.getDatabaseUser() }
1244 raise DatabaseConnectionException(msg)
1245
1246 # -----------------------------------------------------------------------------------------------------------------
1247
1248 def closeDatabaseConnection(self):
1249 """This method close the connection opened by openDatabaseConnection.
1250 """
1251
1252 # Check that the connection object is valid
1253 if self.conn :
1254 # Yes, commit pending transactions
1255 self.conn.commit()
1256
1257 # Close the connection
1258 self.conn.close()
1259 else:
1260 # No, raise a DatabaseConnectionException
1261 msg = "Unable to disconnect from the database. Connection objet is not defined"
1262 raise DatabaseConnectionException(msg)
1263
1264 # -----------------------------------------------------------------------------------------------------------------
1265
1266 def updateVersionInDatabase(self):
1267 """This method updates the version in the database. First it checks for the package to update using a unique
1268 key composed of gar svn path and catalog name. If not found the package is created, otherwise it is updated.
1269 In both case, if data are writtent to the database, entries in history table are created.
1270 """
1271
1272 try:
1273 # Flag used to keep track of the fact we have created a new package. Used in some case to choose behavior
1274 isNewlyCreatedPackage = False
1275
1276 # Check that the connection is defined
1277 if self.conn is None:
1278 # No, raise a DatabaseConnectionException
1279 msg = "Unable to query the database. Connection objet is not defined"
1280 raise DatabaseConnectionException(msg)
1281
1282 # Get a cursor object
1283 cursor = self.conn.cursor(MySQLdb.cursors.DictCursor)
1284
1285 # First retrieve the id_pkg and deletion flag from the database
1286 cursor.execute("select * from UWATCH_PKG_VERSION where PKG_CATALOGNAME = %s", (self.config.getCatalogName() ) )
1287
1288 # if more than one row is found, then report an error
1289 if cursor.rowcount > 1:
1290 msg = self.config.getCatalogName()
1291 raise DuplicatePackageException(msg)
1292
1293 # If rowcount = 0 then the package does not exist. It has to be inserted in the database
1294 if cursor.rowcount == 0:
1295
1296 # Insert the package in the package version table
1297 cursor.execute("insert into UWATCH_PKG_VERSION (PKG_GAR_PATH, PKG_CATALOGNAME, PKG_NAME, PKG_GAR_VERSION, PKG_LAST_GAR_CHECK_DATE) \
1298 values ( %s , %s , %s , %s , %s )" , (self.config.getGarPath(), self.config.getCatalogName(), \
1299 self.config.getPackageName(), self.config.getGarVersion(), self.config.getExecutionDate() ) )
1300
1301 # Flag that we have created a package
1302 isNewlyCreatedPackage = True
1303
1304 # Output some more information if verbose mode is activated
1305 if self.config.getVerbose():
1306 print "Package %(pkg)s added to the database" % { 'pkg' : self.config.getCatalogName() }
1307
1308 # Now the package is inserted. Retrieve the newly inserted package and update other versions
1309 cursor.execute("select * from UWATCH_PKG_VERSION where PKG_GAR_PATH = %s and PKG_CATALOGNAME = %s", \
1310 (self.config.getGarPath(), self.config.getCatalogName() ) )
1311
1312 # Retrieve package information
1313 pkg = cursor.fetchone()
1314
1315 # Test if the deleted flag is set
1316 if pkg["PKG_IS_DELETED"] == 1:
1317 # Yes thus package has to be undeleted
1318 cursor.execute("update UWATCH_PKG_VERSION set PKG_IS_DELETED = 0 where ID_PKG='%s'" , ( pkg["ID_PKG"] ) )
1319
1320 # Output some more information if verbose mode is activated
1321 if self.config.getVerbose():
1322 print "Package %(pkg)s has been undeleted" % { 'pkg' : self.config.getCatalogName() }
1323
1324 # Test if the package has just been created. If yes the history line for gar version has to be inserted
1325 if isNewlyCreatedPackage:
1326 cursor.execute("insert into UWATCH_VERSION_HISTORY ( ID_PKG , HIST_VERSION_TYPE , HIST_VERSION_VALUE , HIST_VERSION_DATE ) values ( %s, %s, %s, %s)" , ( pkg["ID_PKG"], "gar", self.config.getGarVersion() , self.config.getExecutionDate() ) )
1327
1328 # In all cases (update or not) we update the last version check according to the argument
1329 cursor.execute("update UWATCH_PKG_VERSION set PKG_LAST_UPSTREAM_CHECK_DATE = %s , PKG_GAR_PATH = %s where ID_PKG= %s" , ( self.config.getExecutionDate(), self.config.getGarPath() , pkg["ID_PKG"] ) )
1330
1331 # Test if uwatch deactivated flag is set
1332 if self.config.getUwatchDeactivated():
1333 # Yes thus package has to be deactivated
1334 cursor.execute("update UWATCH_PKG_VERSION set PKG_UWATCH_ACTIVATED='0' where ID_PKG= %s" , ( pkg["ID_PKG"] ) )
1335 if self.config.getVerbose():
1336 print "%(pkg) uWatch is deactivated, updating database" % { 'pkg' : self.config.getCatalogName() }
1337 else:
1338 cursor.execute("update UWATCH_PKG_VERSION set PKG_UWATCH_ACTIVATED='1' where ID_PKG= %s" , ( pkg["ID_PKG"] ) )
1339 # Change execution status only if activated
1340 if self.config.getUwatchError():
1341 # Yes thus package has to be updated
1342 cursor.execute("update UWATCH_PKG_VERSION set PKG_LAST_UPSTREAM_CHECK_STATUS='0' where ID_PKG= %s" , ( pkg["ID_PKG"] ) )
1343 if self.config.getVerbose():
1344 print "%(pkg)s uWatch reported an error, updating database" % { 'pkg' : self.config.getCatalogName() }
1345 else:
1346 cursor.execute("update UWATCH_PKG_VERSION set PKG_LAST_UPSTREAM_CHECK_STATUS='1' where ID_PKG= %s" , ( pkg["ID_PKG"] ) )
1347 if self.config.getVerbose():
1348 print "%(pkg)s uWatch successfully ran, updating database" % { 'pkg' : self.config.getCatalogName() }
1349
1350 # Test if upstream version is passed
1351 if self.config.getUpstreamVersion():
1352 # In all cases (update or not) we update the last version check according to the argument
1353 cursor.execute("update UWATCH_PKG_VERSION set PKG_LAST_UPSTREAM_CHECK_DATE = %s , PKG_GAR_PATH = %s where ID_PKG= %s" , ( self.config.getExecutionDate(), self.config.getGarPath() , pkg["ID_PKG"] ) )
1354
1355 # Yes, compare current upstream version from commandline and database
1356 if self.config.getUpstreamVersion() != pkg["PKG_UPSTREAM_VERSION"]:
1357 # Yes thus package has to be updated
1358 cursor.execute("update UWATCH_PKG_VERSION set PKG_UPSTREAM_VERSION = %s where ID_PKG= %s" , ( self.config.getUpstreamVersion(), pkg["ID_PKG"] ) )
1359 cursor.execute("insert into UWATCH_VERSION_HISTORY ( ID_PKG , HIST_VERSION_TYPE , HIST_VERSION_VALUE , HIST_VERSION_DATE ) \
1360 values ( %s, %s, %s, %s)" , ( pkg["ID_PKG"], "upstream", self.config.getUpstreamVersion() , self.config.getExecutionDate() ) )
1361
1362 # Output some more information if verbose mode is activated
1363 if self.config.getVerbose():
1364 print "Upgrading %(pkg)s upstream version from %(current)s to %(next)s" % { 'pkg' : self.config.getCatalogName(), \
1365 'next' : self.config.getUpstreamVersion() , 'current' : pkg["PKG_UPSTREAM_VERSION"] }
1366 else:
1367 # Output some more information if verbose mode is activated
1368 if self.config.getVerbose():
1369 print "%(pkg) GAR version is up to date (%(current)s)" % { 'pkg' : self.config.getCatalogName(), 'current' : self.config.getUpstreamVersion() }
1370
1371 # Test if gar version is passed (it is mandatory to have a value in database)
1372 if self.config.getGarVersion():
1373 # In all cases (update or not) we update the last version check according to the argument
1374 cursor.execute("update UWATCH_PKG_VERSION set PKG_LAST_GAR_CHECK_DATE = %s where ID_PKG= %s" , ( self.config.getExecutionDate(), pkg["ID_PKG"] ) )
1375
1376 # Yes, compare current gar version from commandline and database
1377 if self.config.getGarVersion() != pkg["PKG_GAR_VERSION"]:
1378 # Yes thus package has to be updated
1379 cursor.execute("update UWATCH_PKG_VERSION set PKG_GAR_VERSION = %s where ID_PKG= %s" , ( self.config.getGarVersion(), pkg["ID_PKG"] ) )
1380 cursor.execute("insert into UWATCH_VERSION_HISTORY ( ID_PKG , HIST_VERSION_TYPE , HIST_VERSION_VALUE , HIST_VERSION_DATE ) \
1381 values ( %s, %s, %s, %s)" , ( pkg["ID_PKG"], "gar", self.config.getGarVersion() , self.config.getExecutionDate() ) )
1382
1383 # Output some more information if verbose mode is activated
1384 if self.config.getVerbose():
1385 print "Upgrading %(pkg)s gar version from %(current)s to %(next)s" % { 'pkg' : self.config.getCatalogName(), \
1386 'next' : self.config.getGarVersion() , 'current' : pkg["PKG_GAR_VERSION"] }
1387
1388 # Before closing the connection, there is a last thing to do... storing uwatch configuration into the database
1389
1390 # Yes, compare current gar version from commandline and database
1391 cursor.execute("update UWATCH_PKG_VERSION set PKG_GAR_DISTFILES = %s, PKG_UFILES_REGEXP = %s, PKG_UPSTREAM_MASTER_SITES = %s, PKG_UWATCH_LAST_OUTPUT = %s where ID_PKG= %s" , ( self.config.getGarDistfiles(), self.config.getRegexp(), self.config.getUpstreamURL(), self.config.getUwatchOutput(), pkg["ID_PKG"] ) )
1392
1393 # Close the cursor on the database
1394 cursor.close()
1395
1396 except MySQLdb.Error, e:
1397 msg = "Error %d: %s" % (e.args[0], e.args[1])
1398 raise DatabaseConnectionException(msg)
1399
1400
1401 # -----------------------------------------------------------------------------------------------------------------
1402
1403 def checkArgument(self):
1404
1405 # Variable used to flag that we have a missing argument
1406 argsValid = True
1407
1408 # Gar path is mandatory
1409 if self.config.getGarPath() is None:
1410 print "Error : Gar path is not defined. Please use --gar-path flag, or --help to display help"
1411 argsValid = False
1412
1413 # Gar distfiles is mandatory
1414 if self.config.getGarDistfiles() is None:
1415 print "Error : Gar distfiles is not defined. Please use --gar-distfiles flag, or --help to display help"
1416 argsValid = False
1417
1418 # Gar distfiles is mandatory
1419 if self.config.getUwatchOutput() is None:
1420 print "Error : uWatch output is not defined. Please use --uwatch-output flag, or --help to display help"
1421 argsValid = False
1422
1423 # Catalog name is mandatory
1424 if self.config.getCatalogName() is None:
1425 print "Error : Catalog name is not defined. Please use --catalog-name flag, or --help to display help"
1426 argsValid = False
1427
1428 # Package name is mandatory
1429 if self.config.getPackageName() is None:
1430 print "Error : Package name is not defined. Please use --package-name flag, or --help to display help"
1431 argsValid = False
1432
1433 # Execution date is mandatory
1434 if self.config.getExecutionDate() is None:
1435 print "Error : Execution date is not defined. Please use --execution-date flag, or --help to display help"
1436 argsValid = False
1437
1438 # Gar version is mandatory, other version are optional. Gar the version is the only mandatory in the database
1439 # It has to be passed in argument in case the package does not exist yet
1440 if self.config.getGarVersion() is None:
1441 print "Error : Gar version is not defined. Please use --gar-version flag, or --help to display help"
1442 argsValid = False
1443
1444 # Database schema is mandatory
1445 if self.config.getDatabaseSchema() is None:
1446 print "Error : Database schema is not defined. Please define the value in the ~/.uwatchrc file, use --database-schema flag, or --help to display help"
1447 argsValid = False
1448
1449 # Database host is mandatory
1450 if self.config.getDatabaseHost() is None:
1451 print "Error : Database host is not defined. Please define the value in the ~/.uwatchrc file, use --database-host flag, or --help to display help"
1452 argsValid = False
1453
1454 # Database user is mandatory
1455 if self.config.getDatabaseUser() is None:
1456 print "Error : Database user is not defined. Please define the value in the ~/.uwatchrc file, use --database-user flag, or --help to display help"
1457 argsValid = False
1458
1459 # Database password is mandatory
1460 if self.config.getDatabasePassword() is None:
1461 print "Error : Database password is not defined. Please define the value in the ~/.uwatchrc file, use --database-password flag, or --help to display help"
1462 argsValid = False
1463
1464 # Regexp is mandatory
1465 if self.config.getRegexp() is None:
1466 print "Error : Regexp is not defined. Please use --regexp flag, or --help to display help"
1467 argsValid = False
1468
1469 # UpstreamURL is mandatory
1470 if self.config.getUpstreamURL() is None:
1471 print "Error : Upstream version page URL is not defined. Please use --upstream-url flag, or --help to display help"
1472 argsValid = False
1473
1474 # If arguments are not valid raise an exception
1475 if not argsValid:
1476 raise MissingArgumentException("Some mandatory arguments are missing. Unable to continue.")
1477
1478 # -----------------------------------------------------------------------------------------------------------------
1479
1480 def execute(self, args):
1481
1482 try:
1483
1484 # Initialize configuration
1485 self.config.initialize(args)
1486
1487 # Need a way to check that all options needed are available
1488 self.checkArgument()
1489
1490 # Connection to the database
1491 self.openDatabaseConnection()
1492
1493 # Connection to the database
1494 self.updateVersionInDatabase()
1495
1496 # Connection to the database
1497 self.closeDatabaseConnection()
1498
1499 # Exit after processing, eveythin gis ok, return true to the command processor
1500 return True
1501
1502 except MissingArgumentException as instance:
1503
1504 # Display a cool error message :)
1505 print instance.parameter
1506
1507 # Exits through exception handling, thus return false to the command processor
1508 return False
1509
1510 except InvalidArgumentException as instance:
1511
1512 # Display a cool error message :)
1513 print instance.parameter
1514
1515 # Exits through exception handling, thus return false to the command processor
1516 return False
1517
1518 except DatabaseConnectionException as instance:
1519
1520 # Display a cool error message :)
1521 print instance.message
1522
1523 # Exits through exception handling, thus return false to the command processor
1524 return False
1525
1526 # ---------------------------------------------------------------------------------------------------------------------
1527 #
1528 #
1529 class CommandProcessor(object):
1530 """This class receive commands from the main loop and forward call to concrete command.
1531 """
1532
1533 # -----------------------------------------------------------------------------------------------------------------
1534
1535 def __init__(self):
1536 """Initialize the objects in charge of concrete command processing. Each object instance are stored in a
1537 map using a key which is the action verb used on the command line.
1538 """
1539
1540 # Defines the map storing the concrete commands
1541 self.commandsByName = {}
1542
1543 # Creates all the concrete commands
1544 cmd = CheckUpstreamCommand("check-upstream")
1545 self.commandsByName[cmd.getName()] = cmd
1546
1547 cmd = GetUpstreamLatestVersionCommand("get-upstream-latest-version")
1548 self.commandsByName[cmd.getName()] = cmd
1549
1550 cmd = GetUpstreamVersionListCommand("get-upstream-version-list")
1551 self.commandsByName[cmd.getName()] = cmd
1552
1553 cmd = UpgradeToVersionCommand("upgrade-to-version")
1554 self.commandsByName[cmd.getName()] = cmd
1555
1556 cmd = ReportPackageVersionCommand("update-package-version-database")
1557 self.commandsByName[cmd.getName()] = cmd
1558
1559 # -----------------------------------------------------------------------------------------------------------------
1560
1561 def execute(self, arguments):
1562 """This method checks that an action is supplied and call the action handler
1563 """
1564
1565 # Check that an action verb is supplied. If none an error is returned
1566 if not arguments.commands:
1567 print "Error : no action supplied"
1568 return 1
1569
1570 # The first element in the arguments array is the action verb. Retrieve the command
1571 # using action verb as key
1572 if self.commandsByName.has_key(arguments.commands[0]):
1573 res = self.commandsByName[arguments.commands[0]].execute(arguments)
1574 if res:
1575 return 0
1576 else:
1577 return 1
1578 else:
1579 print "Error : %(action)s action is not supported" % { 'action' : arguments[0] }
1580 return 2
1581
1582
1583 class UwatchRegexGenerator(object):
1584 """Guesses uwatch regexes based on distfiles."""
1585
1586 WS_RE = re.compile(r'\s+')
1587 DIGIT_RE = re.compile(r'\d+')
1588 DIGIT_REMOVAL_RE = re.compile(r'\d+(?:\.\d+)*[a-z]?')
1589 DIGIT_MATCH_MAKE_RE_1 = r'(\d+(?:[\.-]\d+)*[a-z]?)'
1590 DIGIT_MATCH_MAKE_RE_2 = r'(\d+(?:[\.-]\d+)*)'
1591 DIGIT_MATCH_MAKE_RE_3 = r'(\d+(?:\.\d+)*[a-z]?)'
1592 DIGIT_MATCH_MAKE_RE_4 = r'(\d+(?:\.\d+)*)'
1593 ARCHIVE_RE = re.compile(r"\.(?:tar(?:\.(?:gz|bz2))|tgz)?$")
1594
1595 def _ChooseDistfile(self, file_list):
1596 # First heuristic: files with numbers are distfiles
1597 for f in file_list:
1598 if self.ARCHIVE_RE.search(f):
1599 return f
1600 for f in file_list:
1601 if self.DIGIT_RE.search(f):
1602 return f
1603
1604 def _SeparateSoftwareName(self, catalogname, filename):
1605 """Separate the software name from the rest of the file name.
1606
1607 Software name sometimes contains digits, which we don't want to
1608 include in the regexes.
1609 """
1610 # The first approach is to split by '-'
1611 assert filename
1612 parts = filename.split('-')
1613 parts_c_or_v = map(lambda x: self._CanBeSoftwareName(x, catalogname),
1614 parts)
1615 if False in parts_c_or_v:
1616 i = parts_c_or_v.index(False)
1617 else:
1618 i = 1
1619 return '-'.join(parts[:i]), '-' + '-'.join(parts[i:])
1620
1621 def _SeparateArchiveName(self, filename):
1622 if self.ARCHIVE_RE.search(filename):
1623 first_part = self.ARCHIVE_RE.split(filename)[0]
1624 archive_part = ''.join(self.ARCHIVE_RE.findall(filename))
1625 return first_part, archive_part
1626 return filename, ''
1627
1628 def _CanBeSoftwareName(self, s, catalogname):
1629 if s == catalogname:
1630 return True
1631 if re.match(self.DIGIT_MATCH_MAKE_RE_1, s):
1632 return False
1633 # This is stupid. But let's wait for a real world counterexample.
1634 return True
1635
1636 def GenerateRegex(self, catalogname, distnames):
1637 dist_list = self.WS_RE.split(distnames)
1638 dist_file = self._ChooseDistfile(dist_list)
1639 if not dist_file:
1640 return []
1641 dist_file = dist_file.strip()
1642 softwarename, rest_of_filename = self._SeparateSoftwareName(
1643 catalogname, dist_file)
1644 rest_of_filename, archive_part = self._SeparateArchiveName(rest_of_filename)
1645 no_numbers = self.DIGIT_REMOVAL_RE.split(rest_of_filename)
1646 regex_list = [
1647 softwarename + self.DIGIT_MATCH_MAKE_RE_1.join(no_numbers) + archive_part,
1648 softwarename + self.DIGIT_MATCH_MAKE_RE_2.join(no_numbers) + archive_part,
1649 softwarename + self.DIGIT_MATCH_MAKE_RE_3.join(no_numbers) + archive_part,
1650 softwarename + self.DIGIT_MATCH_MAKE_RE_4.join(no_numbers) + archive_part,
1651 ]
1652 return regex_list
1653
1654
1655
1656 # ---------------------------------------------------------------------------------------------------------------------
1657 #
1658 # Fonction principale
1659 #
1660 def main():
1661 """Program main loop. Process args and call concrete command action.
1662 """
1663
1664 # Create the command processor object
1665 commandProcessor = CommandProcessor()
1666
1667 # Parse command line arguments
1668 cliParser = CommandLineParser()
1669
1670 # Call the command line parser
1671 args = cliParser.parse()
1672
1673 # Call the execute method on the command processor. This method is in charge to find the concrete command
1674 return commandProcessor.execute(args)
1675
1676 # Exit with main return code
1677 if __name__ == '__main__':
1678 res = main()
1679 sys.exit(res)

Properties

Name Value
svn:executable *