Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 1 | // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 5 | import 'dart:async'; |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 6 | import 'dart:convert'; |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 7 | import 'dart:io' hide Platform; |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 8 | import 'dart:typed_data'; |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 9 | |
| 10 | import 'package:args/args.dart'; |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 11 | import 'package:http/http.dart' as http; |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 12 | import 'package:path/path.dart' as path; |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 13 | import 'package:process/process.dart'; |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 14 | import 'package:platform/platform.dart' show Platform, LocalPlatform; |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 15 | |
Greg Spencer | 97e7793 | 2018-02-09 15:42:51 -0800 | [diff] [blame] | 16 | const String chromiumRepo = 'https://chromium.googlesource.com/external/github.com/flutter/flutter'; |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 17 | const String githubRepo = 'https://github.com/flutter/flutter.git'; |
| 18 | const String mingitForWindowsUrl = 'https://storage.googleapis.com/flutter_infra/mingit/' |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 19 | '603511c649b00bbef0a6122a827ac419b656bc19/mingit.zip'; |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 20 | const String gsBase = 'gs://flutter_infra'; |
| 21 | const String releaseFolder = '/releases'; |
| 22 | const String gsReleaseFolder = '$gsBase$releaseFolder'; |
| 23 | const String baseUrl = 'https://storage.googleapis.com/flutter_infra'; |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 24 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 25 | /// Exception class for when a process fails to run, so we can catch |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 26 | /// it and provide something more readable than a stack trace. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 27 | class ProcessRunnerException implements Exception { |
| 28 | ProcessRunnerException(this.message, [this.result]); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 29 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 30 | final String message; |
| 31 | final ProcessResult result; |
Greg Spencer | 984a24c | 2018-03-09 18:58:41 -0800 | [diff] [blame] | 32 | int get exitCode => result?.exitCode ?? -1; |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 33 | |
| 34 | @override |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 35 | String toString() { |
| 36 | String output = runtimeType.toString(); |
| 37 | if (message != null) { |
| 38 | output += ': $message'; |
| 39 | } |
| 40 | final String stderr = result?.stderr ?? ''; |
| 41 | if (stderr.isNotEmpty) { |
Greg Spencer | 984a24c | 2018-03-09 18:58:41 -0800 | [diff] [blame] | 42 | output += ':\n$stderr'; |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 43 | } |
| 44 | return output; |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | enum Branch { dev, beta, release } |
| 49 | |
| 50 | String getBranchName(Branch branch) { |
| 51 | switch (branch) { |
| 52 | case Branch.beta: |
| 53 | return 'beta'; |
| 54 | case Branch.dev: |
| 55 | return 'dev'; |
| 56 | case Branch.release: |
| 57 | return 'release'; |
| 58 | } |
| 59 | return null; |
| 60 | } |
| 61 | |
| 62 | Branch fromBranchName(String name) { |
| 63 | switch (name) { |
| 64 | case 'beta': |
| 65 | return Branch.beta; |
| 66 | case 'dev': |
| 67 | return Branch.dev; |
| 68 | case 'release': |
| 69 | return Branch.release; |
| 70 | default: |
| 71 | throw new ArgumentError('Invalid branch name.'); |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | /// A helper class for classes that want to run a process, optionally have the |
| 76 | /// stderr and stdout reported as the process runs, and capture the stdout |
| 77 | /// properly without dropping any. |
| 78 | class ProcessRunner { |
| 79 | ProcessRunner({ |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 80 | ProcessManager processManager, |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 81 | this.subprocessOutput: true, |
| 82 | this.defaultWorkingDirectory, |
| 83 | this.platform: const LocalPlatform(), |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 84 | }) : processManager = processManager ?? const LocalProcessManager() { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 85 | environment = new Map<String, String>.from(platform.environment); |
| 86 | } |
| 87 | |
| 88 | /// The platform to use for a starting environment. |
| 89 | final Platform platform; |
| 90 | |
| 91 | /// Set [subprocessOutput] to show output as processes run. Stdout from the |
| 92 | /// process will be printed to stdout, and stderr printed to stderr. |
| 93 | final bool subprocessOutput; |
| 94 | |
| 95 | /// Set the [processManager] in order to inject a test instance to perform |
| 96 | /// testing. |
| 97 | final ProcessManager processManager; |
| 98 | |
| 99 | /// Sets the default directory used when `workingDirectory` is not specified |
| 100 | /// to [runProcess]. |
| 101 | final Directory defaultWorkingDirectory; |
| 102 | |
| 103 | /// The environment to run processes with. |
| 104 | Map<String, String> environment; |
| 105 | |
| 106 | /// Run the command and arguments in `commandLine` as a sub-process from |
| 107 | /// `workingDirectory` if set, or the [defaultWorkingDirectory] if not. Uses |
| 108 | /// [Directory.current] if [defaultWorkingDirectory] is not set. |
| 109 | /// |
| 110 | /// Set `failOk` if [runProcess] should not throw an exception when the |
| 111 | /// command completes with a a non-zero exit code. |
| 112 | Future<String> runProcess( |
| 113 | List<String> commandLine, { |
| 114 | Directory workingDirectory, |
| 115 | bool failOk: false, |
| 116 | }) async { |
| 117 | workingDirectory ??= defaultWorkingDirectory ?? Directory.current; |
| 118 | if (subprocessOutput) { |
| 119 | stderr.write('Running "${commandLine.join(' ')}" in ${workingDirectory.path}.\n'); |
| 120 | } |
| 121 | final List<int> output = <int>[]; |
| 122 | final Completer<Null> stdoutComplete = new Completer<Null>(); |
| 123 | final Completer<Null> stderrComplete = new Completer<Null>(); |
| 124 | Process process; |
| 125 | Future<int> allComplete() async { |
| 126 | await stderrComplete.future; |
| 127 | await stdoutComplete.future; |
| 128 | return process.exitCode; |
| 129 | } |
| 130 | |
| 131 | try { |
| 132 | process = await processManager.start( |
| 133 | commandLine, |
| 134 | workingDirectory: workingDirectory.absolute.path, |
| 135 | environment: environment, |
| 136 | ); |
| 137 | process.stdout.listen( |
| 138 | (List<int> event) { |
| 139 | output.addAll(event); |
| 140 | if (subprocessOutput) { |
| 141 | stdout.add(event); |
| 142 | } |
| 143 | }, |
| 144 | onDone: () async => stdoutComplete.complete(), |
| 145 | ); |
| 146 | if (subprocessOutput) { |
| 147 | process.stderr.listen( |
| 148 | (List<int> event) { |
| 149 | stderr.add(event); |
| 150 | }, |
| 151 | onDone: () async => stderrComplete.complete(), |
| 152 | ); |
| 153 | } else { |
| 154 | stderrComplete.complete(); |
| 155 | } |
| 156 | } on ProcessException catch (e) { |
| 157 | final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} ' |
| 158 | 'failed with:\n${e.toString()}'; |
| 159 | throw new ProcessRunnerException(message); |
Greg Spencer | 984a24c | 2018-03-09 18:58:41 -0800 | [diff] [blame] | 160 | } on ArgumentError catch (e) { |
| 161 | final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} ' |
| 162 | 'failed with:\n${e.toString()}'; |
| 163 | throw new ProcessRunnerException(message); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 164 | } |
| 165 | |
| 166 | final int exitCode = await allComplete(); |
| 167 | if (exitCode != 0 && !failOk) { |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 168 | final String message = 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} failed'; |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 169 | throw new ProcessRunnerException( |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 170 | message, |
| 171 | new ProcessResult(0, exitCode, null, 'returned $exitCode'), |
| 172 | ); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 173 | } |
Alexander Aprelev | 2f8474f | 2018-03-12 15:44:25 -0700 | [diff] [blame] | 174 | return utf8.decoder.convert(output).trim(); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 175 | } |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 176 | } |
| 177 | |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 178 | typedef Future<Uint8List> HttpReader(Uri url, {Map<String, String> headers}); |
| 179 | |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 180 | /// Creates a pre-populated Flutter archive from a git repo. |
| 181 | class ArchiveCreator { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 182 | /// [tempDir] is the directory to use for creating the archive. The script |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 183 | /// will place several GiB of data there, so it should have available space. |
| 184 | /// |
| 185 | /// The processManager argument is used to inject a mock of [ProcessManager] for |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 186 | /// testing purposes. |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 187 | /// |
| 188 | /// If subprocessOutput is true, then output from processes invoked during |
| 189 | /// archive creation is echoed to stderr and stdout. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 190 | ArchiveCreator( |
| 191 | this.tempDir, |
| 192 | this.outputDir, |
| 193 | this.revision, |
| 194 | this.branch, { |
| 195 | ProcessManager processManager, |
| 196 | bool subprocessOutput: true, |
| 197 | this.platform: const LocalPlatform(), |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 198 | HttpReader httpReader, |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 199 | }) : assert(revision.length == 40), |
| 200 | flutterRoot = new Directory(path.join(tempDir.path, 'flutter')), |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 201 | httpReader = httpReader ?? http.readBytes, |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 202 | _processRunner = new ProcessRunner( |
| 203 | processManager: processManager, |
| 204 | subprocessOutput: subprocessOutput, |
| 205 | platform: platform, |
| 206 | ) { |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 207 | _flutter = path.join( |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 208 | flutterRoot.absolute.path, |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 209 | 'bin', |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 210 | 'flutter', |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 211 | ); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 212 | _processRunner.environment['PUB_CACHE'] = path.join(flutterRoot.absolute.path, '.pub-cache'); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 213 | } |
| 214 | |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 215 | /// The platform to use for the environment and determining which |
| 216 | /// platform we're running on. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 217 | final Platform platform; |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 218 | |
| 219 | /// The branch to build the archive for. The branch must contain [revision]. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 220 | final Branch branch; |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 221 | |
| 222 | /// The git revision hash to build the archive for. This revision has |
| 223 | /// to be available in the [branch], although it doesn't have to be |
| 224 | /// at HEAD, since we clone the branch and then reset to this revision |
| 225 | /// to create the archive. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 226 | final String revision; |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 227 | |
| 228 | /// The flutter root directory in the [tempDir]. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 229 | final Directory flutterRoot; |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 230 | |
| 231 | /// The temporary directory used to build the archive in. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 232 | final Directory tempDir; |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 233 | |
| 234 | /// The directory to write the output file to. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 235 | final Directory outputDir; |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 236 | |
| 237 | final Uri _minGitUri = Uri.parse(mingitForWindowsUrl); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 238 | final ProcessRunner _processRunner; |
| 239 | |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 240 | /// Used to tell the [ArchiveCreator] which function to use for reading |
| 241 | /// bytes from a URL. Used in tests to inject a fake reader. Defaults to |
| 242 | /// [http.readBytes]. |
| 243 | final HttpReader httpReader; |
| 244 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 245 | File _outputFile; |
| 246 | String _version; |
| 247 | String _flutter; |
| 248 | |
| 249 | /// Get the name of the channel as a string. |
| 250 | String get branchName => getBranchName(branch); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 251 | |
| 252 | /// Returns a default archive name when given a Git revision. |
| 253 | /// Used when an output filename is not given. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 254 | String get _archiveName { |
| 255 | final String os = platform.operatingSystem.toLowerCase(); |
Greg Spencer | 29d59e3 | 2018-02-27 13:15:32 -0800 | [diff] [blame] | 256 | // We don't use .tar.xz on Mac because although it can unpack them |
| 257 | // on the command line (with tar), the "Archive Utility" that runs |
| 258 | // when you double-click on them just does some crazy behavior (it |
| 259 | // converts it to a compressed cpio archive, and when you double |
| 260 | // click on that, it converts it back to .tar.xz, without ever |
| 261 | // unpacking it!) So, we use .zip for Mac, and the files are about |
| 262 | // 220MB larger than they need to be. :-( |
| 263 | final String suffix = platform.isLinux ? 'tar.xz' : 'zip'; |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 264 | return 'flutter_${os}_$_version-$branchName.$suffix'; |
| 265 | } |
| 266 | |
| 267 | /// Checks out the flutter repo and prepares it for other operations. |
| 268 | /// |
| 269 | /// Returns the version for this release, as obtained from the git tags. |
| 270 | Future<String> initializeRepo() async { |
| 271 | await _checkoutFlutter(); |
| 272 | _version = await _getVersion(); |
| 273 | return _version; |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 274 | } |
| 275 | |
| 276 | /// Performs all of the steps needed to create an archive. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 277 | Future<File> createArchive() async { |
| 278 | assert(_version != null, 'Must run initializeRepo before createArchive'); |
| 279 | _outputFile = new File(path.join(outputDir.absolute.path, _archiveName)); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 280 | await _installMinGitIfNeeded(); |
| 281 | await _populateCaches(); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 282 | await _archiveFiles(_outputFile); |
| 283 | return _outputFile; |
| 284 | } |
| 285 | |
| 286 | /// Returns the version number of this release, according the to tags in |
| 287 | /// the repo. |
| 288 | Future<String> _getVersion() async { |
| 289 | return _runGit(<String>['describe', '--tags', '--abbrev=0']); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 290 | } |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 291 | |
| 292 | /// Clone the Flutter repo and make sure that the git environment is sane |
| 293 | /// for when the user will unpack it. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 294 | Future<Null> _checkoutFlutter() async { |
| 295 | // We want the user to start out the in the specified branch instead of a |
| 296 | // detached head. To do that, we need to make sure the branch points at the |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 297 | // desired revision. |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 298 | await _runGit(<String>['clone', '-b', branchName, chromiumRepo], workingDirectory: tempDir); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 299 | await _runGit(<String>['reset', '--hard', revision]); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 300 | |
| 301 | // Make the origin point to github instead of the chromium mirror. |
Greg Spencer | c345c1b | 2018-03-14 12:56:44 -0700 | [diff] [blame] | 302 | await _runGit(<String>['remote', 'set-url', 'origin', githubRepo]); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 303 | } |
| 304 | |
| 305 | /// Retrieve the MinGit executable from storage and unpack it. |
| 306 | Future<Null> _installMinGitIfNeeded() async { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 307 | if (!platform.isWindows) { |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 308 | return; |
| 309 | } |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 310 | final Uint8List data = await httpReader(_minGitUri); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 311 | final File gitFile = new File(path.join(tempDir.absolute.path, 'mingit.zip')); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 312 | await gitFile.writeAsBytes(data, flush: true); |
| 313 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 314 | final Directory minGitPath = |
| 315 | new Directory(path.join(flutterRoot.absolute.path, 'bin', 'mingit')); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 316 | await minGitPath.create(recursive: true); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 317 | await _unzipArchive(gitFile, workingDirectory: minGitPath); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 318 | } |
| 319 | |
| 320 | /// Prepare the archive repo so that it has all of the caches warmed up and |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 321 | /// is configured for the user to begin working. |
| 322 | Future<Null> _populateCaches() async { |
| 323 | await _runFlutter(<String>['doctor']); |
| 324 | await _runFlutter(<String>['update-packages']); |
| 325 | await _runFlutter(<String>['precache']); |
| 326 | await _runFlutter(<String>['ide-config']); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 327 | |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 328 | // Create each of the templates, since they will call 'pub get' on |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 329 | // themselves when created, and this will warm the cache with their |
| 330 | // dependencies too. |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 331 | for (String template in <String>['app', 'package', 'plugin']) { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 332 | final String createName = path.join(tempDir.path, 'create_$template'); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 333 | await _runFlutter( |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 334 | <String>['create', '--template=$template', createName], |
| 335 | ); |
| 336 | } |
| 337 | |
| 338 | // Yes, we could just skip all .packages files when constructing |
| 339 | // the archive, but some are checked in, and we don't want to skip |
| 340 | // those. |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 341 | await _runGit(<String>['clean', '-f', '-X', '**/.packages']); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 342 | } |
| 343 | |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 344 | /// Write the archive to the given output file. |
| 345 | Future<Null> _archiveFiles(File outputFile) async { |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 346 | if (outputFile.path.toLowerCase().endsWith('.zip')) { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 347 | await _createZipArchive(outputFile, flutterRoot); |
Greg Spencer | a556ad0 | 2017-12-15 16:40:49 -0800 | [diff] [blame] | 348 | } else if (outputFile.path.toLowerCase().endsWith('.tar.xz')) { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 349 | await _createTarArchive(outputFile, flutterRoot); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 350 | } |
| 351 | } |
| 352 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 353 | Future<String> _runFlutter(List<String> args, {Directory workingDirectory}) { |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 354 | return _processRunner.runProcess( |
| 355 | <String>[_flutter]..addAll(args), |
| 356 | workingDirectory: workingDirectory ?? flutterRoot, |
| 357 | ); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 358 | } |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 359 | |
| 360 | Future<String> _runGit(List<String> args, {Directory workingDirectory}) { |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 361 | return _processRunner.runProcess( |
| 362 | <String>['git']..addAll(args), |
| 363 | workingDirectory: workingDirectory ?? flutterRoot, |
| 364 | ); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 365 | } |
| 366 | |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 367 | /// Unpacks the given zip file into the currentDirectory (if set), or the |
| 368 | /// same directory as the archive. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 369 | Future<String> _unzipArchive(File archive, {Directory workingDirectory}) { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 370 | workingDirectory ??= new Directory(path.dirname(archive.absolute.path)); |
Greg Spencer | c345c1b | 2018-03-14 12:56:44 -0700 | [diff] [blame] | 371 | List<String> commandLine; |
| 372 | if (platform.isWindows) { |
| 373 | commandLine = <String>[ |
| 374 | '7za', |
| 375 | 'x', |
| 376 | archive.absolute.path, |
| 377 | ]; |
| 378 | } else { |
| 379 | commandLine = <String>[ |
| 380 | 'unzip', |
| 381 | archive.absolute.path, |
| 382 | ]; |
| 383 | } |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 384 | return _processRunner.runProcess(commandLine, workingDirectory: workingDirectory); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 385 | } |
| 386 | |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 387 | /// Create a zip archive from the directory source. |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 388 | Future<String> _createZipArchive(File output, Directory source) { |
Greg Spencer | 29d59e3 | 2018-02-27 13:15:32 -0800 | [diff] [blame] | 389 | List<String> commandLine; |
| 390 | if (platform.isWindows) { |
| 391 | commandLine = <String>[ |
| 392 | '7za', |
| 393 | 'a', |
| 394 | '-tzip', |
| 395 | '-mx=9', |
| 396 | output.absolute.path, |
| 397 | path.basename(source.path), |
| 398 | ]; |
| 399 | } else { |
| 400 | commandLine = <String>[ |
| 401 | 'zip', |
| 402 | '-r', |
| 403 | '-9', |
| 404 | output.absolute.path, |
| 405 | path.basename(source.path), |
| 406 | ]; |
| 407 | } |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 408 | return _processRunner.runProcess( |
| 409 | commandLine, |
| 410 | workingDirectory: new Directory(path.dirname(source.absolute.path)), |
| 411 | ); |
Greg Spencer | b7169c1 | 2018-01-09 17:42:42 -0800 | [diff] [blame] | 412 | } |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 413 | |
| 414 | /// Create a tar archive from the directory source. |
| 415 | Future<String> _createTarArchive(File output, Directory source) { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 416 | return _processRunner.runProcess(<String>[ |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 417 | 'tar', |
| 418 | 'cJf', |
| 419 | output.absolute.path, |
| 420 | path.basename(source.absolute.path), |
| 421 | ], workingDirectory: new Directory(path.dirname(source.absolute.path))); |
| 422 | } |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 423 | } |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 424 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 425 | class ArchivePublisher { |
| 426 | ArchivePublisher( |
| 427 | this.tempDir, |
| 428 | this.revision, |
| 429 | this.branch, |
| 430 | this.version, |
| 431 | this.outputFile, { |
| 432 | ProcessManager processManager, |
| 433 | bool subprocessOutput: true, |
| 434 | this.platform: const LocalPlatform(), |
| 435 | }) : assert(revision.length == 40), |
| 436 | platformName = platform.operatingSystem.toLowerCase(), |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 437 | metadataGsPath = '$gsReleaseFolder/${getMetadataFilename(platform)}', |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 438 | _processRunner = new ProcessRunner( |
| 439 | processManager: processManager, |
| 440 | subprocessOutput: subprocessOutput, |
| 441 | ); |
| 442 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 443 | final Platform platform; |
| 444 | final String platformName; |
| 445 | final String metadataGsPath; |
| 446 | final Branch branch; |
| 447 | final String revision; |
| 448 | final String version; |
| 449 | final Directory tempDir; |
| 450 | final File outputFile; |
| 451 | final ProcessRunner _processRunner; |
| 452 | String get branchName => getBranchName(branch); |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 453 | String get destinationArchivePath => '$branchName/$platformName/${path.basename(outputFile.path)}'; |
| 454 | static String getMetadataFilename(Platform platform) => 'releases_${platform.operatingSystem.toLowerCase()}.json'; |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 455 | |
| 456 | /// Publish the archive to Google Storage. |
| 457 | Future<Null> publishArchive() async { |
| 458 | final String destGsPath = '$gsReleaseFolder/$destinationArchivePath'; |
| 459 | await _cloudCopy(outputFile.absolute.path, destGsPath); |
| 460 | assert(tempDir.existsSync()); |
Greg Spencer | 984a24c | 2018-03-09 18:58:41 -0800 | [diff] [blame] | 461 | await _updateMetadata(); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 462 | } |
| 463 | |
Greg Spencer | 05ccf56 | 2018-03-22 13:14:31 -0700 | [diff] [blame] | 464 | Map<String, dynamic> _addRelease(Map<String, dynamic> jsonData) { |
| 465 | jsonData['base_url'] = '$baseUrl$releaseFolder'; |
| 466 | if (!jsonData.containsKey('current_release')) { |
| 467 | jsonData['current_release'] = <String, String>{}; |
| 468 | } |
| 469 | jsonData['current_release'][branchName] = revision; |
| 470 | if (!jsonData.containsKey('releases')) { |
| 471 | jsonData['releases'] = <Map<String, dynamic>>[]; |
| 472 | } |
| 473 | |
| 474 | final Map<String, dynamic> newEntry = <String, dynamic>{}; |
| 475 | newEntry['hash'] = revision; |
| 476 | newEntry['channel'] = branchName; |
| 477 | newEntry['version'] = version; |
| 478 | newEntry['release_date'] = new DateTime.now().toUtc().toIso8601String(); |
| 479 | newEntry['archive'] = destinationArchivePath; |
| 480 | |
| 481 | // Search for any entries with the same hash and channel and remove them. |
| 482 | final List<dynamic> releases = jsonData['releases']; |
| 483 | final List<Map<String, dynamic>> prunedReleases = <Map<String, dynamic>>[]; |
| 484 | for (Map<String, dynamic> entry in releases) { |
| 485 | if (entry['hash'] != newEntry['hash'] || entry['channel'] != newEntry['channel']) { |
| 486 | prunedReleases.add(entry); |
| 487 | } |
| 488 | } |
| 489 | |
| 490 | prunedReleases.add(newEntry); |
| 491 | prunedReleases.sort((Map<String, dynamic> a, Map<String, dynamic> b) { |
| 492 | final DateTime aDate = DateTime.parse(a['release_date']); |
| 493 | final DateTime bDate = DateTime.parse(b['release_date']); |
| 494 | return bDate.compareTo(aDate); |
| 495 | }); |
| 496 | jsonData['releases'] = prunedReleases; |
| 497 | return jsonData; |
| 498 | } |
| 499 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 500 | Future<Null> _updateMetadata() async { |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 501 | // We can't just cat the metadata from the server with 'gsutil cat', because |
| 502 | // Windows wants to echo the commands that execute in gsutil.bat to the |
| 503 | // stdout when we do that. So, we copy the file locally and then read it |
| 504 | // back in. |
| 505 | final File metadataFile = new File( |
| 506 | path.join(tempDir.absolute.path, getMetadataFilename(platform)), |
| 507 | ); |
| 508 | await _runGsUtil(<String>['cp', metadataGsPath, metadataFile.absolute.path]); |
| 509 | final String currentMetadata = metadataFile.readAsStringSync(); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 510 | if (currentMetadata.isEmpty) { |
| 511 | throw new ProcessRunnerException('Empty metadata received from server'); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 512 | } |
| 513 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 514 | Map<String, dynamic> jsonData; |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 515 | try { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 516 | jsonData = json.decode(currentMetadata); |
| 517 | } on FormatException catch (e) { |
| 518 | throw new ProcessRunnerException('Unable to parse JSON metadata received from cloud: $e'); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 519 | } |
| 520 | |
Greg Spencer | 05ccf56 | 2018-03-22 13:14:31 -0700 | [diff] [blame] | 521 | jsonData = _addRelease(jsonData); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 522 | |
Alexandre Ardhuin | 7667db6 | 2018-03-14 06:24:49 +0100 | [diff] [blame] | 523 | const JsonEncoder encoder = const JsonEncoder.withIndent(' '); |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 524 | metadataFile.writeAsStringSync(encoder.convert(jsonData)); |
| 525 | await _cloudCopy(metadataFile.absolute.path, metadataGsPath); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 526 | } |
| 527 | |
Greg Spencer | d9e3715 | 2018-03-20 08:14:08 -0700 | [diff] [blame] | 528 | Future<String> _runGsUtil( |
| 529 | List<String> args, { |
| 530 | Directory workingDirectory, |
| 531 | bool failOk: false, |
| 532 | }) async { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 533 | return _processRunner.runProcess( |
| 534 | <String>['gsutil']..addAll(args), |
| 535 | workingDirectory: workingDirectory, |
| 536 | failOk: failOk, |
| 537 | ); |
| 538 | } |
| 539 | |
| 540 | Future<String> _cloudCopy(String src, String dest) async { |
Greg Spencer | 97e7793 | 2018-02-09 15:42:51 -0800 | [diff] [blame] | 541 | // We often don't have permission to overwrite, but |
| 542 | // we have permission to remove, so that's what we do. |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 543 | await _runGsUtil(<String>['rm', dest], failOk: true); |
Greg Spencer | 97e7793 | 2018-02-09 15:42:51 -0800 | [diff] [blame] | 544 | String mimeType; |
| 545 | if (dest.endsWith('.tar.xz')) { |
| 546 | mimeType = 'application/x-gtar'; |
| 547 | } |
| 548 | if (dest.endsWith('.zip')) { |
| 549 | mimeType = 'application/zip'; |
| 550 | } |
| 551 | if (dest.endsWith('.json')) { |
| 552 | mimeType = 'application/json'; |
| 553 | } |
Greg Spencer | 0b6c193 | 2018-02-22 09:03:10 -0800 | [diff] [blame] | 554 | final List<String> args = <String>[]; |
Greg Spencer | 97e7793 | 2018-02-09 15:42:51 -0800 | [diff] [blame] | 555 | // Use our preferred MIME type for the files we care about |
| 556 | // and let gsutil figure it out for anything else. |
| 557 | if (mimeType != null) { |
| 558 | args.addAll(<String>['-h', 'Content-Type:$mimeType']); |
| 559 | } |
Greg Spencer | 0b6c193 | 2018-02-22 09:03:10 -0800 | [diff] [blame] | 560 | args.addAll(<String>['cp', src, dest]); |
Greg Spencer | 97e7793 | 2018-02-09 15:42:51 -0800 | [diff] [blame] | 561 | return _runGsUtil(args); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 562 | } |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 563 | } |
| 564 | |
| 565 | /// Prepares a flutter git repo to be packaged up for distribution. |
| 566 | /// It mainly serves to populate the .pub-cache with any appropriate Dart |
| 567 | /// packages, and the flutter cache in bin/cache with the appropriate |
| 568 | /// dependencies and snapshots. |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 569 | /// |
| 570 | /// Note that archives contain the executables and customizations for the |
| 571 | /// platform that they are created on. |
| 572 | Future<Null> main(List<String> argList) async { |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 573 | final ArgParser argParser = new ArgParser(); |
| 574 | argParser.addOption( |
| 575 | 'temp_dir', |
| 576 | defaultsTo: null, |
| 577 | help: 'A location where temporary files may be written. Defaults to a ' |
| 578 | 'directory in the system temp folder. Will write a few GiB of data, ' |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 579 | 'so it should have sufficient free space. If a temp_dir is not ' |
| 580 | 'specified, then the default temp_dir will be created, used, and ' |
| 581 | 'removed automatically.', |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 582 | ); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 583 | argParser.addOption('revision', |
| 584 | defaultsTo: null, |
| 585 | help: 'The Flutter git repo revision to build the ' |
| 586 | 'archive with. Must be the full 40-character hash. Required.'); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 587 | argParser.addOption( |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 588 | 'branch', |
| 589 | defaultsTo: null, |
| 590 | allowed: Branch.values.map((Branch branch) => getBranchName(branch)), |
| 591 | help: 'The Flutter branch to build the archive with. Required.', |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 592 | ); |
| 593 | argParser.addOption( |
| 594 | 'output', |
| 595 | defaultsTo: null, |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 596 | help: 'The path to the directory where the output archive should be ' |
| 597 | 'written. If --output is not specified, the archive will be written to ' |
| 598 | "the current directory. If the output directory doesn't exist, it, and " |
| 599 | 'the path to it, will be created.', |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 600 | ); |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 601 | argParser.addFlag( |
| 602 | 'publish', |
| 603 | defaultsTo: false, |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 604 | help: 'If set, will publish the archive to Google Cloud Storage upon ' |
| 605 | 'successful creation of the archive. Will publish under this ' |
| 606 | 'directory: $baseUrl$releaseFolder', |
| 607 | ); |
| 608 | argParser.addFlag( |
| 609 | 'help', |
| 610 | defaultsTo: false, |
| 611 | negatable: false, |
| 612 | help: 'Print help for this command.', |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 613 | ); |
| 614 | |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 615 | final ArgResults args = argParser.parse(argList); |
| 616 | |
Greg Spencer | df79127 | 2018-02-07 12:21:14 -0800 | [diff] [blame] | 617 | if (args['help']) { |
| 618 | print(argParser.usage); |
| 619 | exit(0); |
| 620 | } |
| 621 | |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 622 | void errorExit(String message, {int exitCode = -1}) { |
| 623 | stderr.write('Error: $message\n\n'); |
| 624 | stderr.write('${argParser.usage}\n'); |
| 625 | exit(exitCode); |
| 626 | } |
| 627 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 628 | final String revision = args['revision']; |
| 629 | if (revision.isEmpty) { |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 630 | errorExit('Invalid argument: --revision must be specified.'); |
| 631 | } |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 632 | if (revision.length != 40) { |
| 633 | errorExit('Invalid argument: --revision must be the entire hash, not just a prefix.'); |
| 634 | } |
| 635 | |
| 636 | if (args['branch'].isEmpty) { |
| 637 | errorExit('Invalid argument: --branch must be specified.'); |
| 638 | } |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 639 | |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 640 | Directory tempDir; |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 641 | bool removeTempDir = false; |
| 642 | if (args['temp_dir'] == null || args['temp_dir'].isEmpty) { |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 643 | tempDir = Directory.systemTemp.createTempSync('flutter_'); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 644 | removeTempDir = true; |
| 645 | } else { |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 646 | tempDir = new Directory(args['temp_dir']); |
| 647 | if (!tempDir.existsSync()) { |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 648 | errorExit("Temporary directory ${args['temp_dir']} doesn't exist."); |
| 649 | } |
| 650 | } |
| 651 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 652 | Directory outputDir; |
| 653 | if (args['output'] == null) { |
| 654 | outputDir = tempDir; |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 655 | } else { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 656 | outputDir = new Directory(args['output']); |
| 657 | if (!outputDir.existsSync()) { |
| 658 | outputDir.createSync(recursive: true); |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 659 | } |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 660 | } |
| 661 | |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 662 | final Branch branch = fromBranchName(args['branch']); |
| 663 | final ArchiveCreator creator = new ArchiveCreator(tempDir, outputDir, revision, branch); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 664 | int exitCode = 0; |
| 665 | String message; |
| 666 | try { |
Greg Spencer | 8a2df39 | 2018-02-06 15:32:19 -0800 | [diff] [blame] | 667 | final String version = await creator.initializeRepo(); |
| 668 | final File outputFile = await creator.createArchive(); |
| 669 | if (args['publish']) { |
| 670 | final ArchivePublisher publisher = new ArchivePublisher( |
| 671 | tempDir, |
| 672 | revision, |
| 673 | branch, |
| 674 | version, |
| 675 | outputFile, |
| 676 | ); |
| 677 | await publisher.publishArchive(); |
| 678 | } |
| 679 | } on ProcessRunnerException catch (e) { |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 680 | exitCode = e.exitCode; |
| 681 | message = e.message; |
Greg Spencer | 984a24c | 2018-03-09 18:58:41 -0800 | [diff] [blame] | 682 | } catch (e) { |
| 683 | exitCode = -1; |
| 684 | message = e.toString(); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 685 | } finally { |
| 686 | if (removeTempDir) { |
Michael Goderbauer | 9e51a60 | 2018-01-10 13:37:36 -0800 | [diff] [blame] | 687 | tempDir.deleteSync(recursive: true); |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 688 | } |
| 689 | if (exitCode != 0) { |
| 690 | errorExit(message, exitCode: exitCode); |
| 691 | } |
| 692 | exit(0); |
| 693 | } |
Greg Spencer | f00c902 | 2017-12-15 15:01:30 -0800 | [diff] [blame] | 694 | } |