| 1 | // Copyright 2014 The Flutter 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 | |
| 5 | import 'dart:async'; |
| 6 | import 'dart:io' show exit, stderr; |
| 7 | |
| 8 | import 'package:args/args.dart' ; |
| 9 | import 'package:file/file.dart' ; |
| 10 | import 'package:file/local.dart' ; |
| 11 | |
| 12 | import 'prepare_package/archive_creator.dart'; |
| 13 | import 'prepare_package/archive_publisher.dart'; |
| 14 | import 'prepare_package/common.dart'; |
| 15 | |
| 16 | const FileSystem fs = LocalFileSystem(); |
| 17 | |
| 18 | /// Prepares a flutter git repo to be packaged up for distribution. It mainly |
| 19 | /// serves to populate the .pub-preload-cache with any appropriate Dart |
| 20 | /// packages, and the flutter cache in bin/cache with the appropriate |
| 21 | /// dependencies and snapshots. |
| 22 | /// |
| 23 | /// Archives contain the executables and customizations for the platform that |
| 24 | /// they are created on. |
| 25 | Future<void> main(List<String> rawArguments) async { |
| 26 | final ArgParser argParser = ArgParser(); |
| 27 | argParser.addOption( |
| 28 | 'temp_dir' , |
| 29 | help: |
| 30 | 'A location where temporary files may be written. Defaults to a ' |
| 31 | 'directory in the system temp folder. Will write a few GiB of data, ' |
| 32 | 'so it should have sufficient free space. If a temp_dir is not ' |
| 33 | 'specified, then the default temp_dir will be created, used, and ' |
| 34 | 'removed automatically.' , |
| 35 | ); |
| 36 | argParser.addOption( |
| 37 | 'revision' , |
| 38 | help: |
| 39 | 'The Flutter git repo revision to build the ' |
| 40 | 'archive with. Must be the full 40-character hash. Required.' , |
| 41 | ); |
| 42 | argParser.addOption( |
| 43 | 'branch' , |
| 44 | allowed: Branch.values.map<String>((Branch branch) => branch.name), |
| 45 | help: 'The Flutter branch to build the archive with. Required.' , |
| 46 | ); |
| 47 | argParser.addOption( |
| 48 | 'output' , |
| 49 | help: |
| 50 | 'The path to the directory where the output archive should be ' |
| 51 | 'written. If --output is not specified, the archive will be written to ' |
| 52 | "the current directory. If the output directory doesn't exist, it, and " |
| 53 | 'the path to it, will be created.' , |
| 54 | ); |
| 55 | argParser.addFlag( |
| 56 | 'publish' , |
| 57 | help: |
| 58 | 'If set, will publish the archive to Google Cloud Storage upon ' |
| 59 | 'successful creation of the archive. Will publish under this ' |
| 60 | 'directory: $baseUrl$releaseFolder' , |
| 61 | ); |
| 62 | argParser.addFlag('force' , abbr: 'f' , help: 'Overwrite a previously uploaded package.' ); |
| 63 | argParser.addFlag( |
| 64 | 'dry_run' , |
| 65 | negatable: false, |
| 66 | help: 'Prints gsutil commands instead of executing them.' , |
| 67 | ); |
| 68 | argParser.addFlag('help' , negatable: false, help: 'Print help for this command.' ); |
| 69 | |
| 70 | final ArgResults parsedArguments = argParser.parse(rawArguments); |
| 71 | |
| 72 | if (parsedArguments['help' ] as bool) { |
| 73 | print(argParser.usage); |
| 74 | exit(0); |
| 75 | } |
| 76 | |
| 77 | void errorExit(String message, {int exitCode = -1}) { |
| 78 | stderr.write('Error: $message\n\n' ); |
| 79 | stderr.write(' ${argParser.usage}\n' ); |
| 80 | exit(exitCode); |
| 81 | } |
| 82 | |
| 83 | if (!parsedArguments.wasParsed('revision' )) { |
| 84 | errorExit('Invalid argument: --revision must be specified.' ); |
| 85 | } |
| 86 | final String revision = parsedArguments['revision' ] as String; |
| 87 | if (revision.length != 40) { |
| 88 | errorExit('Invalid argument: --revision must be the entire hash, not just a prefix.' ); |
| 89 | } |
| 90 | |
| 91 | if (!parsedArguments.wasParsed('branch' )) { |
| 92 | errorExit('Invalid argument: --branch must be specified.' ); |
| 93 | } |
| 94 | |
| 95 | final String? tempDirArg = parsedArguments['temp_dir' ] as String?; |
| 96 | final Directory tempDir; |
| 97 | bool removeTempDir = false; |
| 98 | if (tempDirArg == null || tempDirArg.isEmpty) { |
| 99 | tempDir = fs.systemTempDirectory.createTempSync('flutter_package.' ); |
| 100 | removeTempDir = true; |
| 101 | } else { |
| 102 | tempDir = fs.directory(tempDirArg); |
| 103 | if (!tempDir.existsSync()) { |
| 104 | errorExit("Temporary directory $tempDirArg doesn't exist." ); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | final Directory outputDir; |
| 109 | if (parsedArguments['output' ] == null) { |
| 110 | outputDir = tempDir; |
| 111 | } else { |
| 112 | outputDir = fs.directory(parsedArguments['output' ] as String); |
| 113 | if (!outputDir.existsSync()) { |
| 114 | outputDir.createSync(recursive: true); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | final bool publish = parsedArguments['publish' ] as bool; |
| 119 | final bool dryRun = parsedArguments['dry_run' ] as bool; |
| 120 | final Branch branch = Branch.values.byName(parsedArguments['branch' ] as String); |
| 121 | final ArchiveCreator creator = ArchiveCreator( |
| 122 | tempDir, |
| 123 | outputDir, |
| 124 | revision, |
| 125 | branch, |
| 126 | fs: fs, |
| 127 | strict: publish && !dryRun, |
| 128 | ); |
| 129 | int exitCode = 0; |
| 130 | late String message; |
| 131 | try { |
| 132 | final Map<String, String> version = await creator.initializeRepo(); |
| 133 | final File outputFile = await creator.createArchive(); |
| 134 | final ArchivePublisher publisher = ArchivePublisher( |
| 135 | tempDir, |
| 136 | revision, |
| 137 | branch, |
| 138 | version, |
| 139 | outputFile, |
| 140 | dryRun, |
| 141 | fs: fs, |
| 142 | ); |
| 143 | await publisher.generateLocalMetadata(); |
| 144 | if (parsedArguments['publish' ] as bool) { |
| 145 | await publisher.publishArchive(parsedArguments['force' ] as bool); |
| 146 | } |
| 147 | } on PreparePackageException catch (e) { |
| 148 | exitCode = e.exitCode; |
| 149 | message = e.message; |
| 150 | } catch (e) { |
| 151 | exitCode = -1; |
| 152 | message = e.toString(); |
| 153 | } finally { |
| 154 | if (removeTempDir) { |
| 155 | tempDir.deleteSync(recursive: true); |
| 156 | } |
| 157 | if (exitCode != 0) { |
| 158 | errorExit(message, exitCode: exitCode); |
| 159 | } |
| 160 | exit(0); |
| 161 | } |
| 162 | } |
| 163 | |