| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # | 2 # |
| 3 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 3 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 4 # for details. All rights reserved. Use of this source code is governed by a | 4 # for details. All rights reserved. Use of this source code is governed by a |
| 5 # BSD-style license that can be found in the LICENSE file. | 5 # BSD-style license that can be found in the LICENSE file. |
| 6 | 6 |
| 7 # Dart Editor promote and google storage cleanup tools. | 7 # Dart Editor promote tools. |
| 8 | 8 |
| 9 import gsutil | |
| 10 import imp | 9 import imp |
| 11 import optparse | 10 import optparse |
| 12 import os | 11 import os |
| 13 import subprocess | 12 import subprocess |
| 14 import sys | 13 import sys |
| 15 import urllib | 14 import urllib |
| 16 | 15 |
| 17 from os.path import join | 16 from os.path import join |
| 18 | 17 |
| 19 CONTINUOUS = 'gs://dart-editor-archive-continuous' | |
| 20 TRUNK = 'gs://dart-editor-archive-trunk' | |
| 21 TESTING = 'gs://dart-editor-archive-testing' | |
| 22 INTEGRATION = 'gs://dart-editor-archive-integration' | |
| 23 RELEASE = 'gs://dart-editor-archive-release' | |
| 24 INTERNAL = 'gs://dart-editor-archive-internal' | |
| 25 | |
| 26 DART_PATH = os.path.abspath(os.path.join(__file__, '..', '..', '..')) | 18 DART_PATH = os.path.abspath(os.path.join(__file__, '..', '..', '..')) |
| 27 BOT_UTILS = os.path.abspath(os.path.join( | 19 BOT_UTILS = os.path.abspath(os.path.join( |
| 28 DART_PATH, 'tools', 'bots', 'bot_utils.py')) | 20 DART_PATH, 'tools', 'bots', 'bot_utils.py')) |
| 29 | 21 |
| 30 bot_utils = imp.load_source('bot_utils', BOT_UTILS) | 22 bot_utils = imp.load_source('bot_utils', BOT_UTILS) |
| 31 | 23 |
| 32 def _BuildOptions(): | 24 def BuildOptions(): |
| 33 """Setup the argument processing for this program. | 25 usage = """usage: %prog promote [options] |
| 26 where: |
| 27 promote - Will promote builds from raw/signed locations to release |
| 28 locations. |
| 34 | 29 |
| 35 Returns: | 30 Example: Promote revision r29962 on dev channel: |
| 36 the OptionParser to process the CLI | 31 python editor/build/promote.py promote --channel=dev --revision=29962 |
| 37 """ | 32 """ |
| 38 usage = """usage: %prog [options] cleanup|promote | |
| 39 where: | |
| 40 cleanup will cleanup the Google storage continuous bucket | |
| 41 promote will promote code between different stages | |
| 42 | |
| 43 Examples: | |
| 44 cleanup saving the last 150 revisions | |
| 45 python gsTool.py cleanup --keepcount=150 | |
| 46 | |
| 47 promote revision 567 from trunk to integration | |
| 48 python gsTool.py promote --trunk --revision=567""" | |
| 49 | |
| 50 | 33 |
| 51 result = optparse.OptionParser(usage=usage) | 34 result = optparse.OptionParser(usage=usage) |
| 52 result.set_default('gsbucketuri', 'gs://dart-editor-archive-continuous') | 35 |
| 53 result.set_default('keepcount', 1000) | 36 group = optparse.OptionGroup( |
| 54 result.set_default('dryrun', False) | 37 result, 'Promote', 'options used to promote code') |
| 55 result.set_default('continuous', False) | 38 group.add_option( |
| 56 result.set_default('trunk', False) | 39 '--revision', help='The svn revision to promote', action='store') |
| 57 result.set_default('integration', False) | 40 group.add_option( |
| 58 result.set_default('testing', False) | 41 '--channel', type='string', help='Channel to promote.', default=None) |
| 59 group = optparse.OptionGroup(result, 'Cleanup', | |
| 60 'options used to cleanup Google Storage') | |
| 61 group.add_option('--keepcount', | |
| 62 type='int', | |
| 63 help='Numer of Builds to keep.', | |
| 64 action='store') | |
| 65 result.add_option_group(group) | 42 result.add_option_group(group) |
| 66 | 43 |
| 67 group = optparse.OptionGroup(result, 'Promote', | |
| 68 'options used to promote code') | |
| 69 group.add_option('--revision', | |
| 70 help='The svn revision to promote', | |
| 71 action='store') | |
| 72 group.add_option('--continuous', | |
| 73 help='Promote from continuous', | |
| 74 action='store_true') | |
| 75 group.add_option('--trunk', | |
| 76 help='Promote from trunk', | |
| 77 action='store_true') | |
| 78 group.add_option('--internal', | |
| 79 help='Promote from trunk to internal', | |
| 80 action='store_true') | |
| 81 group.add_option('--integration', | |
| 82 help='Promote from integration', | |
| 83 action='store_true') | |
| 84 group.add_option('--channel', type='string', | |
| 85 help='Promote from this channel', | |
| 86 default=None) | |
| 87 result.add_option_group(group) | |
| 88 | |
| 89 result.add_option('--gsbucketuri', | |
| 90 help='Dart Continuous Google Storage bucket URI.', | |
| 91 action='store') | |
| 92 result.add_option('--gsutilloc', | |
| 93 help='location of gsutil the program', | |
| 94 action='store') | |
| 95 result.add_option('--dryrun', help='don\'t do anything that would change' | |
| 96 ' Google Storage', | |
| 97 action='store_true') | |
| 98 result.add_option('--testing', help='user test bucket in ' | |
| 99 ' Google Storage', | |
| 100 action='store_true') | |
| 101 | |
| 102 return result | 44 return result |
| 103 | 45 |
| 104 | 46 |
| 105 def main(): | 47 def main(): |
| 106 """Main entry point for Google Storage Tools.""" | 48 parser = BuildOptions() |
| 107 | |
| 108 parser = _BuildOptions() | |
| 109 (options, args) = parser.parse_args() | 49 (options, args) = parser.parse_args() |
| 110 | 50 |
| 111 if not args: | 51 def die(msg): |
| 112 print 'At least one command must be specified' | 52 print msg |
| 113 parser.print_help() | 53 parser.print_help() |
| 114 sys.exit(1) | 54 sys.exit(1) |
| 115 | 55 |
| 56 if not args: |
| 57 die('At least one command must be specified') |
| 58 |
| 116 if args[0] == 'promote': | 59 if args[0] == 'promote': |
| 117 command = 'promote' | 60 command = 'promote' |
| 118 if options.revision is None: | 61 if options.revision is None: |
| 119 print 'You must specify a --revision to specify which revision to promote' | 62 die('You must specify a --revision to specify which revision to promote') |
| 120 parser.print_help() | |
| 121 sys.exit(3) | |
| 122 | 63 |
| 123 # Make sure revision is a valid integer | 64 # Make sure revision is a valid integer |
| 124 try: | 65 try: |
| 125 _ = int(options.revision) | 66 _ = int(options.revision) |
| 126 except: | 67 except: |
| 127 print 'You must supply a valid integer argument to --revision to promote' | 68 die('You must supply a valid integer argument to --revision to promote') |
| 128 parser.print_help() | |
| 129 sys.exit(3) | |
| 130 | 69 |
| 131 # Make sure options.channel is a valid channel if given | 70 # Make sure options.channel is a valid |
| 132 if options.channel: | 71 if not options.channel: |
| 133 if options.channel not in bot_utils.Channel.ALL_CHANNELS: | 72 die('Specify --channel=be/dev/stable') |
| 134 print 'You must supply a valid channel to --channel to promote' | 73 if options.channel not in bot_utils.Channel.ALL_CHANNELS: |
| 135 parser.print_help() | 74 die('You must supply a valid channel to --channel to promote') |
| 136 sys.exit(3) | 75 else: |
| 76 die('Invalid command specified: {0}. See help below'.format(args[0])) |
| 137 | 77 |
| 138 if not (options.continuous or options.integration or | 78 if command == 'promote': |
| 139 options.testing or options.trunk or options.internal or | 79 _PromoteDartArchiveBuild(options.channel, options.revision) |
| 140 options.channel): | |
| 141 print ('Specify --continuous, --integration, --testing, --trunk or ' | |
| 142 '--channel=be/dev/stable') | |
| 143 parser.print_help() | |
| 144 sys.exit(4) | |
| 145 if options.continuous and options.integration: | |
| 146 print 'continuous and integration can not be specified at the same time' | |
| 147 parser.print_help() | |
| 148 sys.exit(5) | |
| 149 if (options.continuous or options.integration) and options.testing: | |
| 150 print """Warning --continuous or --integration and --testing are | |
| 151 specified. The --testing flag will take precedence and all data will | |
| 152 go to the testing bucket {0}""".format(TESTING) | |
| 153 elif args[0] == 'cleanup': | |
| 154 command = 'cleanup' | |
| 155 if options.keepcount is None: | |
| 156 print 'You must specify --keepcount' | |
| 157 parser.print_help() | |
| 158 sys.exit(6) | |
| 159 else: | |
| 160 print 'Invalid command specified: {0}. See help below'.format(args[0]) | |
| 161 parser.print_help() | |
| 162 sys.exit(2) | |
| 163 | |
| 164 gsu = gsutil.GsUtil(options.dryrun, options.gsutilloc) | |
| 165 if options.testing: | |
| 166 bucket_from = CONTINUOUS | |
| 167 bucket_to = TESTING | |
| 168 print """The --testing attribute is specified. All data will go to the | |
| 169 testing bucket {0}. Press enter to continue""".format(TESTING) | |
| 170 raw_input('Press Enter to continue') | |
| 171 elif options.continuous: | |
| 172 bucket_from = CONTINUOUS | |
| 173 bucket_to = INTEGRATION | |
| 174 elif options.trunk: | |
| 175 bucket_from = TRUNK | |
| 176 bucket_to = INTEGRATION | |
| 177 elif options.integration: | |
| 178 bucket_from = INTEGRATION | |
| 179 bucket_to = RELEASE | |
| 180 elif options.internal: | |
| 181 bucket_from = TRUNK | |
| 182 bucket_to = INTERNAL | |
| 183 | |
| 184 if command == 'cleanup': | |
| 185 #if the testing flag is set remove the date from the testing bucket | |
| 186 if options.testing: | |
| 187 bucket = TESTING | |
| 188 #otherwise use the value passed as --gsbucketuri | |
| 189 else: | |
| 190 bucket = options.gsbucketuri | |
| 191 version_dirs = _ReadBucket(gsu, '{0}/{1}'.format(bucket, '[0-9]*')) | |
| 192 _RemoveElements(gsu, bucket, version_dirs, options.keepcount) | |
| 193 elif command == 'promote': | |
| 194 if options.channel: | |
| 195 _PromoteDartArchiveBuild(options.channel, options.revision) | |
| 196 else: | |
| 197 _PromoteBuild(options.revision, bucket_from, bucket_to) | |
| 198 _UpdateDocs() | |
| 199 | 80 |
| 200 | 81 |
| 201 def _UpdateDocs(): | 82 def UpdateDocs(): |
| 202 try: | 83 try: |
| 203 print 'Updating docs' | 84 print 'Updating docs' |
| 204 url = "http://api.dartlang.org/docs/releases/latest/?force_reload=true" | 85 url = "http://api.dartlang.org/docs/releases/latest/?force_reload=true" |
| 205 f = urllib.urlopen(url) | 86 f = urllib.urlopen(url) |
| 206 f.read() | 87 f.read() |
| 207 print 'Successfully updated api docs' | 88 print 'Successfully updated api docs' |
| 208 except Exception as e: | 89 except Exception as e: |
| 209 print 'Could not update api docs, please manually update them' | 90 print 'Could not update api docs, please manually update them' |
| 210 print 'Failed with: %s' % e | 91 print 'Failed with: %s' % e |
| 211 | 92 |
| 212 def _ReadBucket(gsu, bucket): | |
| 213 """Read the contents of a Google Storage Bucket. | |
| 214 | |
| 215 Args: | |
| 216 gsu: the location of the gsutil program | |
| 217 bucket: the bucket to read the contents of | |
| 218 | |
| 219 Returns: | |
| 220 a list of bucket entries excluding all entries starting with "latest" | |
| 221 """ | |
| 222 _PrintSeparator('_ReadBucket({0}, {1})'.format(gsu, bucket)) | |
| 223 elements = [] | |
| 224 items = gsu.ReadBucket(bucket) | |
| 225 for item in items: | |
| 226 dirpaths = item.split('/') | |
| 227 if len(dirpaths) >= 3: | |
| 228 dirname = dirpaths[3] | |
| 229 if dirname != 'latest': | |
| 230 try: | |
| 231 dirnum = int(dirname) | |
| 232 if not dirnum in elements: | |
| 233 elements.append(dirnum) | |
| 234 except ValueError: | |
| 235 pass | |
| 236 | |
| 237 return elements | |
| 238 | |
| 239 | |
| 240 def _RemoveElements(gsu, bucket, version_dirs, keepcount): | |
| 241 """Remove the selected elements from Google Storage. | |
| 242 | |
| 243 Args: | |
| 244 gsu: the gsutil program to run | |
| 245 bucket: the bucket to remove the dirs from | |
| 246 version_dirs: the dictionary of elements to remove keyed by | |
| 247 svn version number | |
| 248 keepcount: the number of elements to keep | |
| 249 """ | |
| 250 _PrintSeparator('_RemoveElements({0}, version_dirs,' | |
| 251 ' {1}'.format(gsu, keepcount)) | |
| 252 version_dirs_size = len(version_dirs) | |
| 253 delete_count = version_dirs_size - keepcount | |
| 254 if delete_count > 0: | |
| 255 count = 0 | |
| 256 version_dirs.sort() | |
| 257 for gs_dir in version_dirs: | |
| 258 if count < delete_count: | |
| 259 gsu.RemoveAll('{0}/{1}/*'.format(bucket, gs_dir)) | |
| 260 else: | |
| 261 print 'version {0}/{1} will be saved'.format(bucket, gs_dir) | |
| 262 count += 1 | |
| 263 else: | |
| 264 print ('nothing to delete because that are only {0} elemens in the list' | |
| 265 ' and the keep count is set to {0}').format(len(version_dirs), | |
| 266 keepcount) | |
| 267 | |
| 268 | |
| 269 def _PromoteBuild(revision, from_bucket, to_bucket): | |
| 270 """Promote a build from one bucket to another. | |
| 271 | |
| 272 Args: | |
| 273 revision: the revision to promote | |
| 274 from_bucket: the bucket to promote from | |
| 275 to_bucket: the bucket to promote to | |
| 276 """ | |
| 277 | |
| 278 # print the gsutil version | |
| 279 _Gsutil(['version']) | |
| 280 | |
| 281 src = '%s/%s/' % (from_bucket, revision) | |
| 282 srcVersion = src + 'VERSION' | |
| 283 | |
| 284 # copy from continuous/1234 to trunk/1234 | |
| 285 dest = '%s/%s/' % (to_bucket, revision) | |
| 286 destUpdate = dest + 'eclipse-update/' | |
| 287 print 'copying: %s -> %s' % (src, dest) | |
| 288 _Gsutil(['cp', '-a', 'public-read', srcVersion, destUpdate + 'features/']) | |
| 289 _Gsutil(['cp', '-a', 'public-read', srcVersion, destUpdate + 'plugins/']) | |
| 290 _Gsutil(['cp', '-r', '-a', 'public-read', src + '*', dest]) | |
| 291 | |
| 292 # copy from continuous/1234 to trunk/latest | |
| 293 dest = '%s/%s/' % (to_bucket, 'latest') | |
| 294 destUpdate = dest + 'eclipse-update/' | |
| 295 print 'copying: %s -> %s' % (src, dest) | |
| 296 _Gsutil(['cp', '-a', 'public-read', srcVersion, destUpdate + 'features/']) | |
| 297 _Gsutil(['cp', '-a', 'public-read', srcVersion, destUpdate + 'plugins/']) | |
| 298 _Gsutil(['cp', '-r', '-a', 'public-read', src + '*', dest]) | |
| 299 | 93 |
| 300 def _PromoteDartArchiveBuild(channel, revision): | 94 def _PromoteDartArchiveBuild(channel, revision): |
| 301 # These namer objects will be used to create GCS object URIs. For the | 95 # These namer objects will be used to create GCS object URIs. For the |
| 302 # structure we use, please see tools/bots/bot_utils.py:GCSNamer | 96 # structure we use, please see tools/bots/bot_utils.py:GCSNamer |
| 303 raw_namer = bot_utils.GCSNamer(channel, bot_utils.ReleaseType.RAW) | 97 raw_namer = bot_utils.GCSNamer(channel, bot_utils.ReleaseType.RAW) |
| 304 signed_namer = bot_utils.GCSNamer(channel, bot_utils.ReleaseType.SIGNED) | 98 signed_namer = bot_utils.GCSNamer(channel, bot_utils.ReleaseType.SIGNED) |
| 305 release_namer = bot_utils.GCSNamer(channel, bot_utils.ReleaseType.RELEASE) | 99 release_namer = bot_utils.GCSNamer(channel, bot_utils.ReleaseType.RELEASE) |
| 306 | 100 |
| 307 def promote(to_revision): | 101 def promote(to_revision): |
| 308 def safety_check_on_gs_path(gs_path, revision, channel): | 102 def safety_check_on_gs_path(gs_path, revision, channel): |
| 309 if not ((revision == 'latest' or int(revision) > 0) | 103 if not ((revision == 'latest' or int(revision) > 0) |
| 310 and len(channel) > 0 | 104 and len(channel) > 0 |
| 311 and ('%s' % revision) in gs_path | 105 and ('%s' % revision) in gs_path |
| 312 and channel in gs_path): | 106 and channel in gs_path): |
| 313 raise Exception( | 107 raise Exception( |
| 314 "InternalError: Sanity check failed on GS URI: %s" % gs_path) | 108 "InternalError: Sanity check failed on GS URI: %s" % gs_path) |
| 315 | 109 |
| 316 def remove_gs_directory(gs_path): | 110 def remove_gs_directory(gs_path): |
| 317 safety_check_on_gs_path(gs_path, to_revision, channel) | 111 safety_check_on_gs_path(gs_path, to_revision, channel) |
| 318 _Gsutil(['-m', 'rm', '-R', '-f', gs_path]) | 112 Gsutil(['-m', 'rm', '-R', '-f', gs_path]) |
| 319 | 113 |
| 320 # Copy VERSION file. | 114 # Copy VERSION file. |
| 321 from_loc = raw_namer.version_filepath(revision) | 115 from_loc = raw_namer.version_filepath(revision) |
| 322 to_loc = release_namer.version_filepath(to_revision) | 116 to_loc = release_namer.version_filepath(to_revision) |
| 323 _Gsutil(['cp', '-a', 'public-read', from_loc, to_loc]) | 117 Gsutil(['cp', '-a', 'public-read', from_loc, to_loc]) |
| 324 | 118 |
| 325 # Copy sdk directory. | 119 # Copy sdk directory. |
| 326 from_loc = raw_namer.sdk_directory(revision) | 120 from_loc = raw_namer.sdk_directory(revision) |
| 327 to_loc = release_namer.sdk_directory(to_revision) | 121 to_loc = release_namer.sdk_directory(to_revision) |
| 328 remove_gs_directory(to_loc) | 122 remove_gs_directory(to_loc) |
| 329 _Gsutil(['-m', 'cp', '-a', 'public-read', '-R', from_loc, to_loc]) | 123 Gsutil(['-m', 'cp', '-a', 'public-read', '-R', from_loc, to_loc]) |
| 330 | 124 |
| 331 # Copy eclipse update directory. | 125 # Copy eclipse update directory. |
| 332 from_loc = raw_namer.editor_eclipse_update_directory(revision) | 126 from_loc = raw_namer.editor_eclipse_update_directory(revision) |
| 333 to_loc = release_namer.editor_eclipse_update_directory(to_revision) | 127 to_loc = release_namer.editor_eclipse_update_directory(to_revision) |
| 334 remove_gs_directory(to_loc) | 128 remove_gs_directory(to_loc) |
| 335 _Gsutil(['-m', 'cp', '-a', 'public-read', '-R', from_loc, to_loc]) | 129 Gsutil(['-m', 'cp', '-a', 'public-read', '-R', from_loc, to_loc]) |
| 336 | 130 |
| 337 # Copy api-docs zipfile. | 131 # Copy api-docs zipfile. |
| 338 from_loc = raw_namer.apidocs_zipfilepath(revision) | 132 from_loc = raw_namer.apidocs_zipfilepath(revision) |
| 339 to_loc = release_namer.apidocs_zipfilepath(to_revision) | 133 to_loc = release_namer.apidocs_zipfilepath(to_revision) |
| 340 _Gsutil(['-m', 'cp', '-a', 'public-read', from_loc, to_loc]) | 134 Gsutil(['-m', 'cp', '-a', 'public-read', from_loc, to_loc]) |
| 341 | 135 |
| 342 # Copy dartium directory. | 136 # Copy dartium directory. |
| 343 from_loc = raw_namer.dartium_directory(revision) | 137 from_loc = raw_namer.dartium_directory(revision) |
| 344 to_loc = release_namer.dartium_directory(to_revision) | 138 to_loc = release_namer.dartium_directory(to_revision) |
| 345 remove_gs_directory(to_loc) | 139 remove_gs_directory(to_loc) |
| 346 _Gsutil(['-m', 'cp', '-a', 'public-read', '-R', from_loc, to_loc]) | 140 Gsutil(['-m', 'cp', '-a', 'public-read', '-R', from_loc, to_loc]) |
| 347 | 141 |
| 348 # Copy editor zip files. | 142 # Copy editor zip files. |
| 349 target_editor_dir = release_namer.editor_directory(to_revision) | 143 target_editor_dir = release_namer.editor_directory(to_revision) |
| 350 remove_gs_directory(target_editor_dir) | 144 remove_gs_directory(target_editor_dir) |
| 351 for system in ['windows', 'macos', 'linux']: | 145 for system in ['windows', 'macos', 'linux']: |
| 352 for arch in ['ia32', 'x64']: | 146 for arch in ['ia32', 'x64']: |
| 353 from_namer = raw_namer | 147 from_namer = raw_namer |
| 354 # We have signed versions of the editor for windows and macos. | 148 # We have signed versions of the editor for windows and macos. |
| 355 if system == 'windows' or system == 'macos': | 149 if system == 'windows' or system == 'macos': |
| 356 from_namer = signed_namer | 150 from_namer = signed_namer |
| 357 from_loc = from_namer.editor_zipfilepath(revision, system, arch) | 151 from_loc = from_namer.editor_zipfilepath(revision, system, arch) |
| 358 to_loc = release_namer.editor_zipfilepath(to_revision, system, arch) | 152 to_loc = release_namer.editor_zipfilepath(to_revision, system, arch) |
| 359 _Gsutil(['cp', '-a', 'public-read', from_loc, to_loc]) | 153 Gsutil(['cp', '-a', 'public-read', from_loc, to_loc]) |
| 360 _Gsutil(['cp', '-a', 'public-read', from_loc + '.md5sum', | 154 Gsutil(['cp', '-a', 'public-read', from_loc + '.md5sum', |
| 361 to_loc + '.md5sum']) | 155 to_loc + '.md5sum']) |
| 362 | 156 |
| 157 # Copy signed editor installers for macos/windows. |
| 158 for system, extension in [('windows', 'msi'), ('macos', 'dmg')]: |
| 159 for arch in ['ia32', 'x64']: |
| 160 from_loc = signed_namer.editor_installer_filepath( |
| 161 revision, system, arch, extension) |
| 162 to_loc = release_namer.editor_installer_filepath( |
| 163 to_revision, system, arch, extension) |
| 164 Gsutil(['cp', '-a', 'public-read', from_loc, to_loc]) |
| 165 |
| 363 promote(revision) | 166 promote(revision) |
| 364 promote('latest') | 167 promote('latest') |
| 365 | 168 |
| 366 def _PrintSeparator(text): | 169 def Gsutil(cmd): |
| 367 print '================================' | |
| 368 print '== %s' % text | |
| 369 | |
| 370 | |
| 371 def _PrintFailure(text): | |
| 372 print '*****************************' | |
| 373 print '** %s' % text | |
| 374 print '*****************************' | |
| 375 | |
| 376 | |
| 377 def _Gsutil(cmd): | |
| 378 gsutilTool = join(DART_PATH, 'third_party', 'gsutil', 'gsutil') | 170 gsutilTool = join(DART_PATH, 'third_party', 'gsutil', 'gsutil') |
| 379 return _ExecuteCommand([sys.executable, gsutilTool] + cmd) | 171 bot_utils.run([sys.executable, gsutilTool] + cmd) |
| 380 | |
| 381 | |
| 382 def _ExecuteCommand(cmd, directory=None): | |
| 383 """Execute the given command.""" | |
| 384 if directory is not None: | |
| 385 cwd = os.getcwd() | |
| 386 os.chdir(directory) | |
| 387 subprocess.call(cmd, env=os.environ) | |
| 388 if directory is not None: | |
| 389 os.chdir(cwd) | |
| 390 | 172 |
| 391 | 173 |
| 392 if __name__ == '__main__': | 174 if __name__ == '__main__': |
| 393 sys.exit(main()) | 175 sys.exit(main()) |
| OLD | NEW |