| #!/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()) |