| #!/usr/bin/env python3 | 
 | # | 
 | # Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file | 
 | # for details. All rights reserved. Use of this source code is governed by a | 
 | # BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | # Dart SDK promote tools. | 
 |  | 
 | import optparse | 
 | import os | 
 | import sys | 
 | import time | 
 | import urllib | 
 | import bots.bot_utils as bot_utils | 
 |  | 
 | from os.path import join | 
 |  | 
 | DART_PATH = os.path.abspath(os.path.join(__file__, '..', '..')) | 
 | DRY_RUN = False | 
 |  | 
 |  | 
 | def BuildOptions(): | 
 |     usage = """usage: %prog promote [options] | 
 |   where: | 
 |     promote - Will promote builds from raw/signed locations to release | 
 |               locations. | 
 |  | 
 |     Example: Promote version 2.5.0 on the stable channel: | 
 |         python3 tools/promote.py promote --channel=stable --version=2.5.0 | 
 |   """ | 
 |  | 
 |     result = optparse.OptionParser(usage=usage) | 
 |  | 
 |     group = optparse.OptionGroup(result, 'Promote', | 
 |                                  'options used to promote code') | 
 |     group.add_option( | 
 |         '--revision', | 
 |         '--version', | 
 |         help='The version to promote', | 
 |         action='store') | 
 |     group.add_option( | 
 |         '--channel', | 
 |         type='string', | 
 |         help='The channel to promote.', | 
 |         default=None) | 
 |     group.add_option( | 
 |         '--source-channel', | 
 |         type='string', | 
 |         help='The channel to promote from. Defaults to the --channel value.', | 
 |         default=None) | 
 |     group.add_option('--dry', | 
 |                      help='Dry run', | 
 |                      default=False, | 
 |                      action='store_true') | 
 |     result.add_option_group(group) | 
 |  | 
 |     return result | 
 |  | 
 |  | 
 | def main(): | 
 |     parser = BuildOptions() | 
 |     (options, args) = parser.parse_args() | 
 |  | 
 |     def die(msg): | 
 |         print(msg) | 
 |         parser.print_help() | 
 |         sys.exit(1) | 
 |  | 
 |     if not args: | 
 |         die('At least one command must be specified') | 
 |  | 
 |     if args[0] == 'promote': | 
 |         command = 'promote' | 
 |         if options.revision is None: | 
 |             die('You must specify the --version to promote') | 
 |  | 
 |         # Make sure options.channel is a valid | 
 |         if not options.channel: | 
 |             die('Specify --channel=beta/dev/stable') | 
 |         if options.channel not in bot_utils.Channel.ALL_CHANNELS: | 
 |             die('You must supply a valid --channel to promote') | 
 |         if (options.source_channel and | 
 |                 options.source_channel not in bot_utils.Channel.ALL_CHANNELS): | 
 |             die('You must supply a valid --source-channel to promote from') | 
 |     else: | 
 |         die('Invalid command specified: {0}.  See help below'.format(args[0])) | 
 |  | 
 |     if options.dry: | 
 |         global DRY_RUN | 
 |         DRY_RUN = True | 
 |     if command == 'promote': | 
 |         source = options.source_channel or options.channel | 
 |         _PromoteDartArchiveBuild(options.channel, source, options.revision) | 
 |  | 
 |  | 
 | def UpdateDocs(): | 
 |     try: | 
 |         print('Updating docs') | 
 |         url = 'http://api.dartlang.org/docs/releases/latest/?force_reload=true' | 
 |         f = urllib.urlopen(url) | 
 |         f.read() | 
 |         print('Successfully updated api docs') | 
 |     except Exception as e: | 
 |         print('Could not update api docs, please manually update them') | 
 |         print('Failed with: %s' % e) | 
 |  | 
 |  | 
 | def _PromoteDartArchiveBuild(channel, source_channel, revision): | 
 |     # These namer objects will be used to create GCS object URIs. For the | 
 |     # structure we use, please see tools/bots/bot_utils.py:GCSNamer | 
 |     raw_namer = bot_utils.GCSNamer(source_channel, bot_utils.ReleaseType.RAW) | 
 |     signed_namer = bot_utils.GCSNamer(source_channel, | 
 |                                       bot_utils.ReleaseType.SIGNED) | 
 |     release_namer = bot_utils.GCSNamer(channel, bot_utils.ReleaseType.RELEASE) | 
 |  | 
 |     def promote(to_revision): | 
 |  | 
 |         def safety_check_on_gs_path(gs_path, revision, channel): | 
 |             if not (revision != None and len(channel) > 0 and | 
 |                     ('%s' % revision) in gs_path and channel in gs_path): | 
 |                 raise Exception( | 
 |                     'InternalError: Sanity check failed on GS URI: %s' % | 
 |                     gs_path) | 
 |  | 
 |         def exists(gs_path): | 
 |             (_, _, exit_code) = Gsutil(['ls', gs_path], throw_on_error=False) | 
 |             # gsutil will exit 0 if the "directory" exists | 
 |             return exit_code == 0 | 
 |  | 
 |         # Google cloud storage has read-after-write, read-after-update, | 
 |         # and read-after-delete consistency, but not list after delete consistency. | 
 |         # Because gsutil uses list to figure out if it should do the unix styly | 
 |         # copy to or copy into, this means that if the directory is reported as | 
 |         # still being there (after it has been deleted) gsutil will copy | 
 |         # into the directory instead of to the directory. | 
 |         def wait_for_delete_to_be_consistent_with_list(gs_path): | 
 |             if DRY_RUN: | 
 |                 return | 
 |             while exists(gs_path): | 
 |                 time.sleep(1) | 
 |  | 
 |         def remove_gs_directory(gs_path): | 
 |             safety_check_on_gs_path(gs_path, to_revision, channel) | 
 |             # Only delete existing directories | 
 |             if exists(gs_path): | 
 |                 Gsutil(['-m', 'rm', '-R', '-f', gs_path]) | 
 |                 wait_for_delete_to_be_consistent_with_list(gs_path) | 
 |  | 
 |         # Copy the signed sdk directory. | 
 |         from_loc = signed_namer.sdk_directory(revision) | 
 |         to_loc = release_namer.sdk_directory(to_revision) | 
 |         remove_gs_directory(to_loc) | 
 |         has_signed = exists(from_loc) | 
 |         if has_signed: | 
 |             Gsutil(['-m', 'cp', '-R', from_loc, to_loc]) | 
 |             # Because gsutil copies differently to existing directories, we need | 
 |             # to use the base directory for the next recursive copy. | 
 |             to_loc = release_namer.base_directory(to_revision) | 
 |  | 
 |         # Copy the unsigned sdk directory without clobbering signed files. | 
 |         from_loc = raw_namer.sdk_directory(revision) | 
 |         Gsutil(['-m', 'cp', '-n', '-R', from_loc, to_loc]) | 
 |  | 
 |         # Copy api-docs zipfile. | 
 |         from_loc = raw_namer.apidocs_zipfilepath(revision) | 
 |         to_loc = release_namer.apidocs_zipfilepath(to_revision) | 
 |         Gsutil(['-m', 'cp', from_loc, to_loc]) | 
 |  | 
 |         # Copy linux deb and src packages. | 
 |         from_loc = raw_namer.linux_packages_directory(revision) | 
 |         to_loc = release_namer.linux_packages_directory(to_revision) | 
 |         remove_gs_directory(to_loc) | 
 |         Gsutil(['-m', 'cp', '-R', from_loc, to_loc]) | 
 |  | 
 |         # Copy VERSION file. | 
 |         from_loc = raw_namer.version_filepath(revision) | 
 |         to_loc = release_namer.version_filepath(to_revision) | 
 |         Gsutil(['cp', from_loc, to_loc]) | 
 |  | 
 |     promote(revision) | 
 |     promote('latest') | 
 |  | 
 |  | 
 | def Gsutil(cmd, throw_on_error=True): | 
 |     gsutilTool = join(DART_PATH, 'third_party', 'gsutil', 'gsutil') | 
 |     command = [sys.executable, gsutilTool] + cmd | 
 |     if DRY_RUN: | 
 |         print('DRY runnning: %s' % command) | 
 |         return (None, None, 0) | 
 |     return bot_utils.run(command, throw_on_error=throw_on_error) | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     sys.exit(main()) |