Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 1 | // Copyright 2019 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'; |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 6 | import 'dart:io' as io; |
| 7 | |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 8 | import 'package:file/file.dart'; |
| 9 | import 'package:process/process.dart'; |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 10 | |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 11 | import 'google_cloud_storage.dart'; |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 12 | import 'log.dart'; |
| 13 | import 'utils.dart'; |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 14 | |
| 15 | /// Statuses reported by Apple's Notary Server. |
| 16 | /// |
| 17 | /// See more: |
| 18 | /// * https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow |
| 19 | enum NotaryStatus { |
| 20 | pending, |
| 21 | failed, |
| 22 | succeeded, |
| 23 | } |
| 24 | |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 25 | /// Codesign and notarize all files within a [RemoteArchive]. |
| 26 | class FileCodesignVisitor { |
| 27 | FileCodesignVisitor({ |
| 28 | required this.commitHash, |
| 29 | required this.codesignCertName, |
| 30 | required this.codesignUserName, |
| 31 | required this.appSpecificPassword, |
| 32 | required this.codesignAppstoreId, |
| 33 | required this.codesignTeamId, |
| 34 | required this.codesignFilepaths, |
| 35 | required this.fileSystem, |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 36 | required this.rootDirectory, |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 37 | required this.processManager, |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 38 | required this.googleCloudStorage, |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 39 | this.production = false, |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 40 | this.notarizationTimerDuration = const Duration(seconds: 5), |
Xilai Zhang | 36249b3 | 2022-08-12 09:55:04 -0700 | [diff] [blame] | 41 | }) { |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 42 | entitlementsFile = rootDirectory.childFile('Entitlements.plist')..writeAsStringSync(_entitlementsFileContents); |
| 43 | remoteDownloadsDir = rootDirectory.childDirectory('downloads')..createSync(); |
| 44 | codesignedZipsDir = rootDirectory.childDirectory('codesigned_zips')..createSync(); |
Xilai Zhang | 36249b3 | 2022-08-12 09:55:04 -0700 | [diff] [blame] | 45 | } |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 46 | |
| 47 | /// Temp [Directory] to download/extract files to. |
| 48 | /// |
| 49 | /// This file will be deleted if [validateAll] completes successfully. |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 50 | final Directory rootDirectory; |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 51 | final FileSystem fileSystem; |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 52 | final ProcessManager processManager; |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 53 | final GoogleCloudStorage googleCloudStorage; |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 54 | |
| 55 | final String commitHash; |
| 56 | final String codesignCertName; |
| 57 | final String codesignUserName; |
| 58 | final String appSpecificPassword; |
| 59 | final String codesignAppstoreId; |
| 60 | final String codesignTeamId; |
| 61 | final bool production; |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 62 | final Duration notarizationTimerDuration; |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 63 | |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 64 | // TODO(xilaizhang): add back utitlity in later splits |
| 65 | Set<String> fileWithEntitlements = <String>{}; |
| 66 | Set<String> fileWithoutEntitlements = <String>{}; |
| 67 | Set<String> fileConsumed = <String>{}; |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 68 | Set<String> directoriesVisited = <String>{}; |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 69 | List<String> codesignFilepaths; |
| 70 | |
| 71 | late final File entitlementsFile; |
| 72 | late final Directory remoteDownloadsDir; |
| 73 | late final Directory codesignedZipsDir; |
| 74 | |
| 75 | int _remoteDownloadIndex = 0; |
| 76 | int get remoteDownloadIndex => _remoteDownloadIndex++; |
| 77 | |
| 78 | static const String _entitlementsFileContents = ''' |
| 79 | <?xml version="1.0" encoding="UTF-8"?> |
| 80 | <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| 81 | <plist version="1.0"> |
| 82 | <dict> |
| 83 | <key>com.apple.security.cs.allow-jit</key> |
| 84 | <true/> |
| 85 | <key>com.apple.security.cs.allow-unsigned-executable-memory</key> |
| 86 | <true/> |
| 87 | <key>com.apple.security.cs.allow-dyld-environment-variables</key> |
| 88 | <true/> |
| 89 | <key>com.apple.security.network.client</key> |
| 90 | <true/> |
| 91 | <key>com.apple.security.network.server</key> |
| 92 | <true/> |
| 93 | <key>com.apple.security.cs.disable-library-validation</key> |
| 94 | <true/> |
| 95 | </dict> |
| 96 | </plist> |
| 97 | '''; |
Xilai Zhang | 697fa8f | 2022-09-02 10:04:49 -0700 | [diff] [blame] | 98 | static final RegExp _notarytoolStatusCheckPattern = RegExp(r'[ ]*status: ([a-zA-z ]+)'); |
Xilai Zhang | 5092fc8 | 2022-09-08 15:01:17 -0700 | [diff] [blame] | 99 | static final RegExp _notarytoolRequestPattern = RegExp(r'id: ([a-z0-9-]+)'); |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 100 | |
Xilai Zhang | 36249b3 | 2022-08-12 09:55:04 -0700 | [diff] [blame] | 101 | static const String fixItInstructions = ''' |
| 102 | Codesign test failed. |
| 103 | |
| 104 | We compared binary files in engine artifacts with those listed in |
| 105 | entitlement.txt and withoutEntitlements.txt, and the binary files do not match. |
| 106 | *entitlements.txt is the configuartion file encoded in engine artifact zip, |
| 107 | built by BUILD.gn and Ninja, to detail the list of entitlement files. |
| 108 | Either an expected file was not found in *entitlements.txt, or an unexpected |
| 109 | file was found in entitlements.txt. |
| 110 | |
| 111 | This usually happens during an engine roll. |
| 112 | If this is a valid change, then BUILD.gn needs to be changed. |
| 113 | Binaries that will run on a macOS host require entitlements, and |
| 114 | binaries that run on an iOS device must NOT have entitlements. |
| 115 | For example, if this is a new binary that runs on macOS host, add it |
| 116 | to [entitlements.txt] file inside the zip artifact produced by BUILD.gn. |
| 117 | If this is a new binary that needs to be run on iOS device, add it |
| 118 | to [withoutEntitlements.txt]. |
| 119 | If there are obsolete binaries in entitlements configuration files, please delete or |
| 120 | update these file paths accordingly. |
| 121 | '''; |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 122 | |
| 123 | /// The entrance point of examining and code signing an engine artifact. |
| 124 | Future<void> validateAll() async { |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 125 | await Future<void>.value(null); |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 126 | log.info('Codesigned all binaries in ${rootDirectory.path}'); |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 127 | |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 128 | await rootDirectory.delete(recursive: true); |
| 129 | } |
| 130 | |
| 131 | /// Retrieve engine artifact from google cloud storage and kick start a |
| 132 | /// recursive visit of its contents. |
| 133 | /// |
| 134 | /// Invokes [visitDirectory] to recursively visit the contents of the remote |
| 135 | /// zip. Also downloads, notarizes and uploads the engine artifact. |
| 136 | Future<void> processRemoteZip({ |
| 137 | required String artifactFilePath, |
| 138 | required Directory parentDirectory, |
| 139 | }) async { |
| 140 | final FileSystem fs = rootDirectory.fileSystem; |
| 141 | |
| 142 | // namespace by hashcode otherwise there will be collisions |
| 143 | final String localFilePath = '${artifactFilePath.hashCode}_${fs.path.basename(artifactFilePath)}'; |
| 144 | |
| 145 | // download the zip file |
| 146 | final File originalFile = await googleCloudStorage.downloadEngineArtifact( |
| 147 | from: artifactFilePath, |
| 148 | destination: remoteDownloadsDir.childFile(localFilePath).path, |
| 149 | ); |
| 150 | |
| 151 | await unzip( |
| 152 | inputZip: originalFile, |
| 153 | outDir: parentDirectory, |
| 154 | processManager: processManager, |
| 155 | ); |
| 156 | |
| 157 | //extract entitlements file. |
| 158 | fileWithEntitlements = await parseEntitlements(parentDirectory, true); |
| 159 | fileWithoutEntitlements = await parseEntitlements(parentDirectory, false); |
| 160 | log.info('parsed binaries with entitlements are $fileWithEntitlements'); |
| 161 | log.info('parsed binaries without entitlements are $fileWithEntitlements'); |
| 162 | |
| 163 | // recursively visit extracted files |
| 164 | await visitDirectory(directory: parentDirectory, entitlementParentPath: artifactFilePath); |
| 165 | |
| 166 | final File codesignedFile = codesignedZipsDir.childFile(localFilePath); |
| 167 | |
| 168 | await zip( |
| 169 | inputDir: parentDirectory, |
| 170 | outputZip: codesignedFile, |
| 171 | processManager: processManager, |
| 172 | ); |
| 173 | |
| 174 | // notarize |
| 175 | await notarize(codesignedFile); |
| 176 | |
| 177 | await googleCloudStorage.uploadEngineArtifact( |
| 178 | from: codesignedFile.path, |
| 179 | destination: artifactFilePath, |
| 180 | ); |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 181 | } |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 182 | |
| 183 | /// Visit a [Directory] type while examining the file system extracted from an artifact. |
| 184 | Future<void> visitDirectory({ |
| 185 | required Directory directory, |
| 186 | required String entitlementParentPath, |
| 187 | }) async { |
Xilai Zhang | 36249b3 | 2022-08-12 09:55:04 -0700 | [diff] [blame] | 188 | log.info('Visiting directory ${directory.absolute.path}'); |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 189 | if (directoriesVisited.contains(directory.absolute.path)) { |
| 190 | log.warning( |
| 191 | 'Warning! You are visiting a directory that has been visited before, the directory is ${directory.absolute.path}'); |
| 192 | } |
| 193 | directoriesVisited.add(directory.absolute.path); |
| 194 | final List<FileSystemEntity> entities = await directory.list().toList(); |
| 195 | for (FileSystemEntity entity in entities) { |
| 196 | if (entity is io.Directory) { |
| 197 | await visitDirectory( |
| 198 | directory: directory.childDirectory(entity.basename), |
| 199 | entitlementParentPath: entitlementParentPath, |
| 200 | ); |
| 201 | continue; |
| 202 | } |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 203 | if (entity.basename == 'entitlements.txt' || entity.basename == 'without_entitlements.txt') { |
| 204 | continue; |
| 205 | } |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 206 | final FileType childType = getFileType( |
| 207 | entity.absolute.path, |
| 208 | processManager, |
| 209 | ); |
| 210 | if (childType == FileType.zip) { |
| 211 | await visitEmbeddedZip( |
| 212 | zipEntity: entity, |
| 213 | entitlementParentPath: entitlementParentPath, |
| 214 | ); |
Xilai Zhang | 36249b3 | 2022-08-12 09:55:04 -0700 | [diff] [blame] | 215 | } else if (childType == FileType.binary) { |
| 216 | await visitBinaryFile(binaryFile: entity as File, entitlementParentPath: entitlementParentPath); |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 217 | } |
Xilai Zhang | 36249b3 | 2022-08-12 09:55:04 -0700 | [diff] [blame] | 218 | log.info('Child file of directory ${directory.basename} is ${entity.basename}'); |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 219 | } |
| 220 | } |
| 221 | |
| 222 | /// Unzip an [EmbeddedZip] and visit its children. |
| 223 | Future<void> visitEmbeddedZip({ |
| 224 | required FileSystemEntity zipEntity, |
| 225 | required String entitlementParentPath, |
| 226 | }) async { |
Xilai Zhang | 36249b3 | 2022-08-12 09:55:04 -0700 | [diff] [blame] | 227 | log.info('This embedded file is ${zipEntity.path} and entitlementParentPath is $entitlementParentPath'); |
| 228 | final String currentFileName = zipEntity.basename; |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 229 | final Directory newDir = rootDirectory.childDirectory('embedded_zip_${zipEntity.absolute.path.hashCode}'); |
Xilai Zhang | 46c6c54 | 2022-08-01 10:35:03 -0700 | [diff] [blame] | 230 | await unzip( |
| 231 | inputZip: zipEntity, |
| 232 | outDir: newDir, |
| 233 | processManager: processManager, |
| 234 | ); |
| 235 | |
| 236 | // the virtual file path is advanced by the name of the embedded zip |
| 237 | final String currentZipEntitlementPath = '$entitlementParentPath/$currentFileName'; |
| 238 | await visitDirectory( |
| 239 | directory: newDir, |
| 240 | entitlementParentPath: currentZipEntitlementPath, |
| 241 | ); |
| 242 | await zipEntity.delete(); |
| 243 | await zip( |
| 244 | inputDir: newDir, |
| 245 | outputZip: zipEntity, |
| 246 | processManager: processManager, |
| 247 | ); |
| 248 | } |
Xilai Zhang | 36249b3 | 2022-08-12 09:55:04 -0700 | [diff] [blame] | 249 | |
| 250 | /// Visit and codesign a binary with / without entitlement. |
| 251 | /// |
| 252 | /// At this stage, the virtual [entitlementCurrentPath] accumulated through the recursive visit, is compared |
| 253 | /// against the paths extracted from [fileWithEntitlements], to help determine if this file should be signed |
| 254 | /// with entitlements. |
| 255 | Future<void> visitBinaryFile({required File binaryFile, required String entitlementParentPath}) async { |
| 256 | final String currentFileName = binaryFile.basename; |
| 257 | final String entitlementCurrentPath = '$entitlementParentPath/$currentFileName'; |
| 258 | |
| 259 | if (!fileWithEntitlements.contains(entitlementCurrentPath) && |
| 260 | !fileWithoutEntitlements.contains(entitlementCurrentPath)) { |
| 261 | log.severe('The system has detected a binary file at $entitlementCurrentPath.' |
| 262 | 'but it is not in the entitlements configuartion files you provided.' |
| 263 | 'if this is a new engine artifact, please add it to one of the entitlements.txt files'); |
| 264 | throw CodesignException(fixItInstructions); |
| 265 | } |
| 266 | log.info('signing file at path ${binaryFile.absolute.path}'); |
| 267 | log.info('the virtual entitlement path associated with file is $entitlementCurrentPath'); |
| 268 | log.info('the decision to sign with entitlement is ${fileWithEntitlements.contains(entitlementCurrentPath)}'); |
| 269 | final List<String> args = <String>[ |
| 270 | 'codesign', |
| 271 | '-f', // force |
| 272 | '-s', // use the cert provided by next argument |
| 273 | codesignCertName, |
| 274 | binaryFile.absolute.path, |
| 275 | '--timestamp', // add a secure timestamp |
| 276 | '--options=runtime', // hardened runtime |
| 277 | if (fileWithEntitlements.contains(entitlementCurrentPath)) ...<String>[ |
| 278 | '--entitlements', |
| 279 | entitlementsFile.absolute.path |
| 280 | ], |
| 281 | ]; |
| 282 | final io.ProcessResult result = await processManager.run(args); |
| 283 | if (result.exitCode != 0) { |
| 284 | throw CodesignException( |
| 285 | 'Failed to codesign ${binaryFile.absolute.path} with args: ${args.join(' ')}\n' |
| 286 | 'stdout:\n${(result.stdout as String).trim()}' |
| 287 | 'stderr:\n${(result.stderr as String).trim()}', |
| 288 | ); |
| 289 | } |
| 290 | fileConsumed.add(entitlementCurrentPath); |
| 291 | } |
Xilai Zhang | 6a8052c | 2022-08-18 10:33:04 -0700 | [diff] [blame] | 292 | |
| 293 | /// Extract entitlements configurations from downloaded zip files. |
| 294 | /// |
| 295 | /// Parse and store codesign configurations detailed in configuration files. |
| 296 | /// File paths of entilement files and non entitlement files will be parsed and stored in [fileWithEntitlements]. |
| 297 | Future<Set<String>> parseEntitlements(Directory parent, bool entitlements) async { |
| 298 | final String entitlementFilePath = entitlements |
| 299 | ? fileSystem.path.join(parent.path, 'entitlements.txt') |
| 300 | : fileSystem.path.join(parent.path, 'without_entitlements.txt'); |
| 301 | if (!(await fileSystem.file(entitlementFilePath).exists())) { |
| 302 | throw CodesignException('$entitlementFilePath not found \n' |
| 303 | 'make sure you have provided them along with the engine artifacts \n'); |
| 304 | } |
| 305 | |
| 306 | final Set<String> fileWithEntitlements = <String>{}; |
| 307 | fileWithEntitlements.addAll(await fileSystem.file(entitlementFilePath).readAsLines()); |
| 308 | return fileWithEntitlements; |
| 309 | } |
Xilai Zhang | 697fa8f | 2022-09-02 10:04:49 -0700 | [diff] [blame] | 310 | |
| 311 | /// Upload a zip archive to the notary service and verify the build succeeded. |
| 312 | /// |
| 313 | /// The apple notarization service will unzip the artifact, validate all |
| 314 | /// binaries are properly codesigned, and notarize the entire archive. |
| 315 | Future<void> notarize(File file) async { |
| 316 | final Completer<void> completer = Completer<void>(); |
| 317 | final String uuid = uploadZipToNotary(file); |
| 318 | |
| 319 | Future<void> callback(Timer timer) async { |
| 320 | final bool notaryFinished = checkNotaryJobFinished(uuid); |
| 321 | if (notaryFinished) { |
| 322 | timer.cancel(); |
| 323 | log.info('successfully notarized ${file.path}'); |
| 324 | completer.complete(); |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | // check on results |
| 329 | Timer.periodic( |
Xilai Zhang | 177ac76 | 2022-09-22 13:41:59 -0700 | [diff] [blame^] | 330 | notarizationTimerDuration, |
Xilai Zhang | 697fa8f | 2022-09-02 10:04:49 -0700 | [diff] [blame] | 331 | callback, |
| 332 | ); |
| 333 | await completer.future; |
| 334 | } |
| 335 | |
Xilai Zhang | 697fa8f | 2022-09-02 10:04:49 -0700 | [diff] [blame] | 336 | /// Make a request to the notary service to see if the notary job is finished. |
| 337 | /// |
| 338 | /// A return value of true means that notarization finished successfully, |
| 339 | /// false means that the job is still pending. If the notarization fails, this |
| 340 | /// function will throw a [ConductorException]. |
| 341 | bool checkNotaryJobFinished(String uuid) { |
| 342 | final List<String> args = <String>[ |
| 343 | 'xcrun', |
| 344 | 'notarytool', |
| 345 | 'info', |
| 346 | uuid, |
| 347 | '--password', |
| 348 | appSpecificPassword, |
| 349 | '--apple-id', |
| 350 | codesignAppstoreId, |
| 351 | '--team-id', |
| 352 | codesignTeamId, |
| 353 | ]; |
| 354 | |
| 355 | log.info('checking notary status with ${args.join(' ')}'); |
| 356 | final io.ProcessResult result = processManager.runSync(args); |
| 357 | final String combinedOutput = (result.stdout as String) + (result.stderr as String); |
| 358 | |
| 359 | final RegExpMatch? match = _notarytoolStatusCheckPattern.firstMatch(combinedOutput); |
| 360 | |
| 361 | if (match == null) { |
| 362 | throw CodesignException( |
| 363 | 'Malformed output from "${args.join(' ')}"\n${combinedOutput.trim()}', |
| 364 | ); |
| 365 | } |
| 366 | |
| 367 | final String status = match.group(1)!; |
| 368 | |
| 369 | if (status == 'Accepted') { |
| 370 | return true; |
| 371 | } |
| 372 | if (status == 'In Progress') { |
| 373 | log.info('job $uuid still pending'); |
| 374 | return false; |
| 375 | } |
| 376 | throw CodesignException('Notarization failed with: $status\n$combinedOutput'); |
| 377 | } |
Xilai Zhang | 5092fc8 | 2022-09-08 15:01:17 -0700 | [diff] [blame] | 378 | |
| 379 | /// Upload artifact to Apple notary service. |
| 380 | String uploadZipToNotary(File localFile, [int retryCount = 3, int sleepTime = 1]) { |
| 381 | while (retryCount > 0) { |
| 382 | final List<String> args = <String>[ |
| 383 | 'xcrun', |
| 384 | 'notarytool', |
| 385 | 'submit', |
| 386 | localFile.absolute.path, |
| 387 | '--apple-id', |
| 388 | codesignAppstoreId, |
| 389 | '--password', |
| 390 | appSpecificPassword, |
| 391 | '--team-id', |
| 392 | codesignTeamId, |
| 393 | ]; |
| 394 | |
| 395 | log.info('uploading ${args.join(' ')}'); |
| 396 | final io.ProcessResult result = processManager.runSync(args); |
| 397 | if (result.exitCode != 0) { |
| 398 | throw CodesignException( |
| 399 | 'Command "${args.join(' ')}" failed with exit code ${result.exitCode}\nStdout: ${result.stdout}\nStderr: ${result.stderr}'); |
| 400 | } |
| 401 | |
| 402 | final String combinedOutput = (result.stdout as String) + (result.stderr as String); |
| 403 | final RegExpMatch? match; |
| 404 | match = _notarytoolRequestPattern.firstMatch(combinedOutput); |
| 405 | |
| 406 | if (match == null) { |
| 407 | log.warning('Failed to upload to the notary service with args: ${args.join(' ')}'); |
| 408 | log.warning('{combinedOutput.trim()}'); |
| 409 | retryCount -= 1; |
| 410 | log.warning('Trying again $retryCount more time${retryCount > 1 ? 's' : ''}...'); |
| 411 | io.sleep(Duration(seconds: sleepTime)); |
| 412 | continue; |
| 413 | } |
| 414 | |
| 415 | final String requestUuid = match.group(1)!; |
| 416 | log.info('RequestUUID for ${localFile.path} is: $requestUuid'); |
| 417 | |
| 418 | return requestUuid; |
| 419 | } |
| 420 | log.warning('The upload to notary service failed after retries, and' |
| 421 | ' the output format does not match the current notary tool version.' |
| 422 | ' If after inspecting the output, you believe the process finished ' |
| 423 | 'successfully but was not detected, please contact flutter release engineers'); |
| 424 | throw CodesignException('Failed to upload ${localFile.path} to the notary service'); |
| 425 | } |
Xilai Zhang | 098a4b6 | 2022-07-15 13:56:04 -0700 | [diff] [blame] | 426 | } |