scripts/release/release.py
changeset 1834 0589bf1395c5
parent 1827 c03995a6a88e
child 1835 3f30b7b14c57
equal deleted inserted replaced
1833:9df2e9a67081 1834:0589bf1395c5
    46 import re
    46 import re
    47 import subprocess
    47 import subprocess
    48 import sys
    48 import sys
    49 
    49 
    50 import error
    50 import error
       
    51 import log
    51 import util
    52 import util
    52 
    53 
    53 
    54 
    54 # Default repository URLs for Melange and the Google release
    55 # Default repository URLs for Melange and the Google release
    55 # repository.
    56 # repository.
    78     """An unexpected state was encountered by an automated step."""
    79     """An unexpected state was encountered by an automated step."""
    79 
    80 
    80 
    81 
    81 class FileAccessError(Error):
    82 class FileAccessError(Error):
    82     """An error occured while accessing a file."""
    83     """An error occured while accessing a file."""
    83 
       
    84 
       
    85 def error(msg):
       
    86     """Log an error message."""
       
    87     print util.colorize(msg, util.RED, bold=True)
       
    88 
       
    89 
       
    90 def info(msg):
       
    91     """Log an informational message."""
       
    92     print util.colorize(msg, util.GREEN)
       
    93 
    84 
    94 
    85 
    95 def confirm(prompt, default=False):
    86 def confirm(prompt, default=False):
    96     """Ask a yes/no question and return the answer.
    87     """Ask a yes/no question and return the answer.
    97 
    88 
   119         elif answer in ('y', 'yes'):
   110         elif answer in ('y', 'yes'):
   120             return True
   111             return True
   121         elif answer in ('n', 'no'):
   112         elif answer in ('n', 'no'):
   122             return False
   113             return False
   123         else:
   114         else:
   124             error('Please answer yes or no.')
   115             log.error('Please answer yes or no.')
   125 
   116 
   126 
   117 
   127 def getString(prompt):
   118 def getString(prompt):
   128     """Prompt for and return a string."""
   119     """Prompt for and return a string."""
   129     try:
   120     try:
   140     while True:
   131     while True:
   141         value_str = getString(prompt)
   132         value_str = getString(prompt)
   142         try:
   133         try:
   143             return int(value_str)
   134             return int(value_str)
   144         except ValueError:
   135         except ValueError:
   145             error('Please enter a number. You entered "%s".' % value_str)
   136             log.error('Please enter a number. You entered "%s".' % value_str)
   146 
   137 
   147 
   138 
   148 def getChoice(intro, prompt, choices, done=None, suggest=None):
   139 def getChoice(intro, prompt, choices, done=None, suggest=None):
   149     """Prompt for and return a choice from a menu.
   140     """Prompt for and return a choice from a menu.
   150 
   141 
   172             print '%s%2d. %s%s' % (indent, i+1, entry, done_text)
   163             print '%s%2d. %s%s' % (indent, i+1, entry, done_text)
   173         print
   164         print
   174         choice = getNumber(prompt)
   165         choice = getNumber(prompt)
   175         if 0 < choice <= len(choices):
   166         if 0 < choice <= len(choices):
   176             return choice-1
   167             return choice-1
   177         error('%d is not a valid choice between %d and %d' %
   168         log.error('%d is not a valid choice between %d and %d' %
   178               (choice, 1, len(choices)))
   169                   (choice, 1, len(choices)))
   179         print
   170         print
   180 
   171 
   181 
   172 
   182 def fileToLines(path):
   173 def fileToLines(path):
   183     """Read a file and return it as a list of lines."""
   174     """Read a file and return it as a list of lines."""
   542 
   533 
   543         Will also select the latest release branch, if any, so that
   534         Will also select the latest release branch, if any, so that
   544         the end state is a fully ready to function release
   535         the end state is a fully ready to function release
   545         environment.
   536         environment.
   546         """
   537         """
   547         info('Checking out the release repository')
   538         log.info('Checking out the release repository')
   548 
   539 
   549         # Check out a sparse view of the relevant repository paths.
   540         # Check out a sparse view of the relevant repository paths.
   550         self.wc.checkout(self.release_repos, depth='immediates')
   541         self.wc.checkout(self.release_repos, depth='immediates')
   551         self.wc.update('vendor', depth='immediates')
   542         self.wc.update('vendor', depth='immediates')
   552         self.wc.update('branches', depth='immediates')
   543         self.wc.update('branches', depth='immediates')
   596 
   587 
   597         """
   588         """
   598         if release is None:
   589         if release is None:
   599             self.branch = None
   590             self.branch = None
   600             self.branch_dir = None
   591             self.branch_dir = None
   601             info('No release branch available')
   592             log.info('No release branch available')
   602         else:
   593         else:
   603             self.wc.update()
   594             self.wc.update()
   604             assert self.wc.exists('branches/' + release)
   595             assert self.wc.exists('branches/' + release)
   605             linesToFile(self.path(self.BRANCH_FILE), [release])
   596             linesToFile(self.path(self.BRANCH_FILE), [release])
   606             self.branch = release
   597             self.branch = release
   607             self.branch_dir = 'branches/' + release
   598             self.branch_dir = 'branches/' + release
   608             self.wc.update(self.branch_dir, depth='infinity')
   599             self.wc.update(self.branch_dir, depth='infinity')
   609             info('Working on branch ' + self.branch)
   600             log.info('Working on branch ' + self.branch)
   610 
   601 
   611     def _branchPath(self, path):
   602     def _branchPath(self, path):
   612         """Return the given path with the release branch path prepended."""
   603         """Return the given path with the release branch path prepended."""
   613         assert self.branch_dir is not None
   604         assert self.branch_dir is not None
   614         return os.path.join(self.branch_dir, path)
   605         return os.path.join(self.branch_dir, path)
   727             # google-specific patches.
   718             # google-specific patches.
   728             self._addAppYaml()
   719             self._addAppYaml()
   729             self._applyGooglePatches()
   720             self._applyGooglePatches()
   730 
   721 
   731             # All done!
   722             # All done!
   732             info('Melange release %s imported and googlified' % self.branch)
   723             log.info('Melange release %s imported and googlified' % self.branch)
   733 
   724 
   734     @requires_branch
   725     @requires_branch
   735     @pristine_wc
   726     @pristine_wc
   736     def cherryPickChange(self):
   727     def cherryPickChange(self):
   737         """Cherry-pick a change from the Melange trunk"""
   728         """Cherry-pick a change from the Melange trunk"""
   762                 updated_patchlevel = True
   753                 updated_patchlevel = True
   763             else:
   754             else:
   764                 out.append(line)
   755                 out.append(line)
   765 
   756 
   766         if not updated_patchlevel:
   757         if not updated_patchlevel:
   767             error('Failed to update Google patch revision')
   758             log.error('Failed to update Google patch revision')
   768             error('Cherry-picking failed')
   759             log.error('Cherry-picking failed')
   769 
   760 
   770         linesToFile(yaml_path, out)
   761         linesToFile(yaml_path, out)
   771 
   762 
   772         info('Check the diff about to be committed with:')
   763         log.info('Check the diff about to be committed with:')
   773         info('svn diff ' + self.wc.path(self.branch_dir))
   764         log.info('svn diff ' + self.wc.path(self.branch_dir))
   774         if not confirm('Commit this change?'):
   765         if not confirm('Commit this change?'):
   775             raise AbortedByUser('Cherry-pick aborted')
   766             raise AbortedByUser('Cherry-pick aborted')
   776         self.wc.commit(message)
   767         self.wc.commit(message)
   777         info('Cherry-picked r%d from the Melange trunk.' % rev)
   768         log.info('Cherry-picked r%d from the Melange trunk.' % rev)
   778 
   769 
   779     MENU_ORDER = [
   770     MENU_ORDER = [
   780         update,
   771         update,
   781         switchToBranch,
   772         switchToBranch,
   782         importTag,
   773         importTag,
   811             try:
   802             try:
   812                 choice = getChoice('Main menu:', 'Your choice?',
   803                 choice = getChoice('Main menu:', 'Your choice?',
   813                                    self.MENU_STRINGS, done=done,
   804                                    self.MENU_STRINGS, done=done,
   814                                    suggest=suggested_next)
   805                                    suggest=suggested_next)
   815             except (KeyboardInterrupt, AbortedByUser):
   806             except (KeyboardInterrupt, AbortedByUser):
   816                 info('Exiting.')
   807                 log.info('Exiting.')
   817                 return
   808                 return
   818             try:
   809             try:
   819                 self.MENU_ORDER[choice](self)
   810                 self.MENU_ORDER[choice](self)
   820             except Error, e:
   811             except Error, e:
   821                 error(str(e))
   812                 log.error(str(e))
   822             else:
   813             else:
   823                 done.append(choice)
   814                 done.append(choice)
   824                 last_choice = choice
   815                 last_choice = choice
   825 
   816 
   826 
   817 
   834     if len(argv) >= 2:
   825     if len(argv) >= 2:
   835         release_repos = argv[1]
   826         release_repos = argv[1]
   836     if len(argv) == 3:
   827     if len(argv) == 3:
   837         upstream_repos = argv[2]
   828         upstream_repos = argv[2]
   838 
   829 
   839     info('Release repository: ' + release_repos)
   830     log.init('release.log')
   840     info('Upstream repository: ' + upstream_repos)
   831 
       
   832     log.info('Release repository: ' + release_repos)
       
   833     log.info('Upstream repository: ' + upstream_repos)
   841 
   834 
   842     r = ReleaseEnvironment(os.path.abspath('_release_'),
   835     r = ReleaseEnvironment(os.path.abspath('_release_'),
   843                            release_repos,
   836                            release_repos,
   844                            upstream_repos)
   837                            upstream_repos)
   845     r.interactiveMenu()
   838     r.interactiveMenu()