blob: 4d32d19a3ef3512af18305cbbb014ee5a9258c76 [file] [log] [blame]
#!/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())