1// Copyright 2016 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 5import 'dart:async'; 6 7import 'package:meta/meta.dart'; 8 9import 'base/common.dart'; 10import 'base/context.dart'; 11import 'base/file_system.dart'; 12import 'base/io.dart' show SocketException; 13import 'base/logger.dart'; 14import 'base/net.dart'; 15import 'base/os.dart'; 16import 'base/platform.dart'; 17import 'globals.dart'; 18 19/// A tag for a set of development artifacts that need to be cached. 20class DevelopmentArtifact { 21 22 const DevelopmentArtifact._(this.name, {this.unstable = false}); 23 24 /// The name of the artifact. 25 /// 26 /// This should match the flag name in precache.dart 27 final String name; 28 29 /// Whether this artifact should be unavailable on stable branches. 30 final bool unstable; 31 32 /// Artifacts required for Android development. 33 static const DevelopmentArtifact android = DevelopmentArtifact._('android'); 34 35 /// Artifacts required for iOS development. 36 static const DevelopmentArtifact iOS = DevelopmentArtifact._('ios'); 37 38 /// Artifacts required for web development. 39 static const DevelopmentArtifact web = DevelopmentArtifact._('web', unstable: true); 40 41 /// Artifacts required for desktop macOS. 42 static const DevelopmentArtifact macOS = DevelopmentArtifact._('macos', unstable: true); 43 44 /// Artifacts required for desktop Windows. 45 static const DevelopmentArtifact windows = DevelopmentArtifact._('windows', unstable: true); 46 47 /// Artifacts required for desktop Linux. 48 static const DevelopmentArtifact linux = DevelopmentArtifact._('linux', unstable: true); 49 50 /// Artifacts required for Fuchsia. 51 static const DevelopmentArtifact fuchsia = DevelopmentArtifact._('fuchsia', unstable: true); 52 53 /// Artifacts required for the Flutter Runner. 54 static const DevelopmentArtifact flutterRunner = DevelopmentArtifact._('flutter_runner', unstable: true); 55 56 /// Artifacts required for any development platform. 57 static const DevelopmentArtifact universal = DevelopmentArtifact._('universal'); 58 59 /// The values of DevelopmentArtifacts. 60 static final List<DevelopmentArtifact> values = <DevelopmentArtifact>[ 61 android, 62 iOS, 63 web, 64 macOS, 65 windows, 66 linux, 67 fuchsia, 68 universal, 69 flutterRunner, 70 ]; 71} 72 73/// A wrapper around the `bin/cache/` directory. 74class Cache { 75 /// [rootOverride] is configurable for testing. 76 /// [artifacts] is configurable for testing. 77 Cache({ Directory rootOverride, List<CachedArtifact> artifacts }) : _rootOverride = rootOverride { 78 if (artifacts == null) { 79 _artifacts.add(MaterialFonts(this)); 80 _artifacts.add(AndroidEngineArtifacts(this)); 81 _artifacts.add(IOSEngineArtifacts(this)); 82 _artifacts.add(GradleWrapper(this)); 83 _artifacts.add(FlutterWebSdk(this)); 84 _artifacts.add(FlutterSdk(this)); 85 _artifacts.add(WindowsEngineArtifacts(this)); 86 _artifacts.add(MacOSEngineArtifacts(this)); 87 _artifacts.add(LinuxEngineArtifacts(this)); 88 _artifacts.add(LinuxFuchsiaSDKArtifacts(this)); 89 _artifacts.add(MacOSFuchsiaSDKArtifacts(this)); 90 _artifacts.add(FlutterRunnerSDKArtifacts(this)); 91 for (String artifactName in IosUsbArtifacts.artifactNames) { 92 _artifacts.add(IosUsbArtifacts(artifactName, this)); 93 } 94 } else { 95 _artifacts.addAll(artifacts); 96 } 97 } 98 99 static const List<String> _hostsBlockedInChina = <String> [ 100 'storage.googleapis.com', 101 ]; 102 103 final Directory _rootOverride; 104 final List<CachedArtifact> _artifacts = <CachedArtifact>[]; 105 106 // Initialized by FlutterCommandRunner on startup. 107 static String flutterRoot; 108 109 // Whether to cache artifacts for all platforms. Defaults to only caching 110 // artifacts for the current platform. 111 bool includeAllPlatforms = false; 112 113 static RandomAccessFile _lock; 114 static bool _lockEnabled = true; 115 116 /// Turn off the [lock]/[releaseLockEarly] mechanism. 117 /// 118 /// This is used by the tests since they run simultaneously and all in one 119 /// process and so it would be a mess if they had to use the lock. 120 @visibleForTesting 121 static void disableLocking() { 122 _lockEnabled = false; 123 } 124 125 /// Turn on the [lock]/[releaseLockEarly] mechanism. 126 /// 127 /// This is used by the tests. 128 @visibleForTesting 129 static void enableLocking() { 130 _lockEnabled = true; 131 } 132 133 /// Lock the cache directory. 134 /// 135 /// This happens automatically on startup (see [FlutterCommandRunner.runCommand]). 136 /// 137 /// Normally the lock will be held until the process exits (this uses normal 138 /// POSIX flock semantics). Long-lived commands should release the lock by 139 /// calling [Cache.releaseLockEarly] once they are no longer touching the cache. 140 static Future<void> lock() async { 141 if (!_lockEnabled) 142 return; 143 assert(_lock == null); 144 final File lockFile = 145 fs.file(fs.path.join(flutterRoot, 'bin', 'cache', 'lockfile')); 146 try { 147 _lock = lockFile.openSync(mode: FileMode.write); 148 } on FileSystemException catch (e) { 149 printError('Failed to open or create the artifact cache lockfile: "$e"'); 150 printError('Please ensure you have permissions to create or open ' 151 '${lockFile.path}'); 152 throwToolExit('Failed to open or create the lockfile'); 153 } 154 bool locked = false; 155 bool printed = false; 156 while (!locked) { 157 try { 158 _lock.lockSync(); 159 locked = true; 160 } on FileSystemException { 161 if (!printed) { 162 printTrace('Waiting to be able to obtain lock of Flutter binary artifacts directory: ${_lock.path}'); 163 printStatus('Waiting for another flutter command to release the startup lock...'); 164 printed = true; 165 } 166 await Future<void>.delayed(const Duration(milliseconds: 50)); 167 } 168 } 169 } 170 171 /// Releases the lock. This is not necessary unless the process is long-lived. 172 static void releaseLockEarly() { 173 if (!_lockEnabled || _lock == null) 174 return; 175 _lock.closeSync(); 176 _lock = null; 177 } 178 179 /// Checks if the current process owns the lock for the cache directory at 180 /// this very moment; throws a [StateError] if it doesn't. 181 static void checkLockAcquired() { 182 if (_lockEnabled && _lock == null && platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') { 183 throw StateError( 184 'The current process does not own the lock for the cache directory. This is a bug in Flutter CLI tools.', 185 ); 186 } 187 } 188 189 String _dartSdkVersion; 190 191 String get dartSdkVersion { 192 if (_dartSdkVersion == null) { 193 // Make the version string more customer-friendly. 194 // Changes '2.1.0-dev.8.0.flutter-4312ae32' to '2.1.0 (build 2.1.0-dev.8.0 4312ae32)' 195 final String justVersion = platform.version.split(' ')[0]; 196 _dartSdkVersion = justVersion.replaceFirstMapped(RegExp(r'(\d+\.\d+\.\d+)(.+)'), (Match match) { 197 final String noFlutter = match[2].replaceAll('.flutter-', ' '); 198 return '${match[1]} (build ${match[1]}$noFlutter)'; 199 }); 200 } 201 return _dartSdkVersion; 202 } 203 204 /// The current version of the Flutter engine the flutter tool will download. 205 String get engineRevision { 206 _engineRevision ??= getVersionFor('engine'); 207 return _engineRevision; 208 } 209 String _engineRevision; 210 211 static Cache get instance => context.get<Cache>(); 212 213 /// Return the top-level directory in the cache; this is `bin/cache`. 214 Directory getRoot() { 215 if (_rootOverride != null) 216 return fs.directory(fs.path.join(_rootOverride.path, 'bin', 'cache')); 217 else 218 return fs.directory(fs.path.join(flutterRoot, 'bin', 'cache')); 219 } 220 221 /// Return a directory in the cache dir. For `pkg`, this will return `bin/cache/pkg`. 222 Directory getCacheDir(String name) { 223 final Directory dir = fs.directory(fs.path.join(getRoot().path, name)); 224 if (!dir.existsSync()) { 225 dir.createSync(recursive: true); 226 os.chmod(dir, '755'); 227 } 228 return dir; 229 } 230 231 /// Return the top-level directory for artifact downloads. 232 Directory getDownloadDir() => getCacheDir('downloads'); 233 234 /// Return the top-level mutable directory in the cache; this is `bin/cache/artifacts`. 235 Directory getCacheArtifacts() => getCacheDir('artifacts'); 236 237 /// Get a named directory from with the cache's artifact directory; for example, 238 /// `material_fonts` would return `bin/cache/artifacts/material_fonts`. 239 Directory getArtifactDirectory(String name) { 240 return getCacheArtifacts().childDirectory(name); 241 } 242 243 MapEntry<String, String> get dyLdLibEntry { 244 if (_dyLdLibEntry != null) { 245 return _dyLdLibEntry; 246 } 247 final List<String> paths = <String>[]; 248 for (CachedArtifact artifact in _artifacts) { 249 final String currentPath = artifact.dyLdLibPath; 250 if (currentPath.isNotEmpty) { 251 paths.add(currentPath); 252 } 253 } 254 _dyLdLibEntry = MapEntry<String, String>('DYLD_LIBRARY_PATH', paths.join(':')); 255 return _dyLdLibEntry; 256 } 257 MapEntry<String, String> _dyLdLibEntry; 258 259 /// The web sdk has to be co-located with the dart-sdk so that they can share source 260 /// code. 261 Directory getWebSdkDirectory() { 262 return getRoot().childDirectory('flutter_web_sdk'); 263 } 264 265 String getVersionFor(String artifactName) { 266 final File versionFile = fs.file(fs.path.join( 267 _rootOverride?.path ?? flutterRoot, 'bin', 'internal', 268 '$artifactName.version')); 269 return versionFile.existsSync() ? versionFile.readAsStringSync().trim() : null; 270 } 271 272 String getStampFor(String artifactName) { 273 final File stampFile = getStampFileFor(artifactName); 274 return stampFile.existsSync() ? stampFile.readAsStringSync().trim() : null; 275 } 276 277 void setStampFor(String artifactName, String version) { 278 getStampFileFor(artifactName).writeAsStringSync(version); 279 } 280 281 File getStampFileFor(String artifactName) { 282 return fs.file(fs.path.join(getRoot().path, '$artifactName.stamp')); 283 } 284 285 /// Returns `true` if either [entity] is older than the tools stamp or if 286 /// [entity] doesn't exist. 287 bool isOlderThanToolsStamp(FileSystemEntity entity) { 288 final File flutterToolsStamp = getStampFileFor('flutter_tools'); 289 return isOlderThanReference(entity: entity, referenceFile: flutterToolsStamp); 290 } 291 292 bool isUpToDate() => _artifacts.every((CachedArtifact artifact) => artifact.isUpToDate()); 293 294 Future<String> getThirdPartyFile(String urlStr, String serviceName) async { 295 final Uri url = Uri.parse(urlStr); 296 final Directory thirdPartyDir = getArtifactDirectory('third_party'); 297 298 final Directory serviceDir = fs.directory(fs.path.join(thirdPartyDir.path, serviceName)); 299 if (!serviceDir.existsSync()) { 300 serviceDir.createSync(recursive: true); 301 os.chmod(serviceDir, '755'); 302 } 303 304 final File cachedFile = fs.file(fs.path.join(serviceDir.path, url.pathSegments.last)); 305 if (!cachedFile.existsSync()) { 306 try { 307 await _downloadFile(url, cachedFile); 308 } catch (e) { 309 throwToolExit('Failed to fetch third-party artifact $url: $e'); 310 } 311 } 312 313 return cachedFile.path; 314 } 315 316 /// Update the cache to contain all `requiredArtifacts`. 317 Future<void> updateAll(Set<DevelopmentArtifact> requiredArtifacts) async { 318 if (!_lockEnabled) { 319 return; 320 } 321 try { 322 for (CachedArtifact artifact in _artifacts) { 323 if (!artifact.isUpToDate()) { 324 await artifact.update(requiredArtifacts); 325 } 326 } 327 } on SocketException catch (e) { 328 if (_hostsBlockedInChina.contains(e.address?.host)) { 329 printError( 330 'Failed to retrieve Flutter tool dependencies: ${e.message}.\n' 331 'If you\'re in China, please see this page: ' 332 'https://flutter.dev/community/china', 333 emphasis: true, 334 ); 335 } 336 rethrow; 337 } 338 } 339 340 Future<bool> areRemoteArtifactsAvailable({ 341 String engineVersion, 342 bool includeAllPlatforms = true, 343 }) async { 344 final bool includeAllPlatformsState = cache.includeAllPlatforms; 345 bool allAvailible = true; 346 cache.includeAllPlatforms = includeAllPlatforms; 347 for (CachedArtifact cachedArtifact in _artifacts) { 348 if (cachedArtifact is EngineCachedArtifact) { 349 allAvailible &= await cachedArtifact.checkForArtifacts(engineVersion); 350 } 351 } 352 cache.includeAllPlatforms = includeAllPlatformsState; 353 return allAvailible; 354 } 355} 356 357/// An artifact managed by the cache. 358abstract class CachedArtifact { 359 CachedArtifact(this.name, this.cache, this.developmentArtifacts); 360 361 final String name; 362 final Cache cache; 363 364 // The name of the stamp file. Defaults to the same as the 365 // artifact name. 366 String get stampName => name; 367 368 /// Returns a string to be set as environment DYLD_LIBARY_PATH variable 369 String get dyLdLibPath => ''; 370 371 /// All development artifacts this cache provides. 372 final Set<DevelopmentArtifact> developmentArtifacts; 373 374 Directory get location => cache.getArtifactDirectory(name); 375 String get version => cache.getVersionFor(name); 376 377 /// Keep track of the files we've downloaded for this execution so we 378 /// can delete them after completion. We don't delete them right after 379 /// extraction in case [update] is interrupted, so we can restart without 380 /// starting from scratch. 381 final List<File> _downloadedFiles = <File>[]; 382 383 bool isUpToDate() { 384 if (!location.existsSync()) { 385 return false; 386 } 387 if (version != cache.getStampFor(stampName)) { 388 return false; 389 } 390 return isUpToDateInner(); 391 } 392 393 Future<void> update(Set<DevelopmentArtifact> requiredArtifacts) async { 394 // If the set of required artifacts does not include any from this cache, 395 // then we can claim we are up to date to skip downloading. 396 if (!requiredArtifacts.any(developmentArtifacts.contains)) { 397 printTrace('Artifact $this is not required, skipping update.'); 398 return; 399 } 400 if (!location.existsSync()) { 401 try { 402 location.createSync(recursive: true); 403 } on FileSystemException catch (err) { 404 printError(err.toString()); 405 throwToolExit( 406 'Failed to create directory for flutter cache at ${location.path}. ' 407 'Flutter may be missing permissions in its cache directory.' 408 ); 409 } 410 } 411 await updateInner(); 412 cache.setStampFor(stampName, version); 413 _removeDownloadedFiles(); 414 } 415 416 /// Clear any zip/gzip files downloaded. 417 void _removeDownloadedFiles() { 418 for (File f in _downloadedFiles) { 419 f.deleteSync(); 420 for (Directory d = f.parent; d.absolute.path != cache.getDownloadDir().absolute.path; d = d.parent) { 421 if (d.listSync().isEmpty) { 422 d.deleteSync(); 423 } else { 424 break; 425 } 426 } 427 } 428 } 429 430 /// Hook method for extra checks for being up-to-date. 431 bool isUpToDateInner() => true; 432 433 /// Template method to perform artifact update. 434 Future<void> updateInner(); 435 436 String get _storageBaseUrl { 437 final String overrideUrl = platform.environment['FLUTTER_STORAGE_BASE_URL']; 438 if (overrideUrl == null) 439 return 'https://storage.googleapis.com'; 440 _maybeWarnAboutStorageOverride(overrideUrl); 441 return overrideUrl; 442 } 443 444 Uri _toStorageUri(String path) => Uri.parse('$_storageBaseUrl/$path'); 445 446 /// Download an archive from the given [url] and unzip it to [location]. 447 Future<void> _downloadArchive(String message, Uri url, Directory location, bool verifier(File f), void extractor(File f, Directory d)) { 448 return _withDownloadFile('${flattenNameSubdirs(url)}', (File tempFile) async { 449 if (!verifier(tempFile)) { 450 final Status status = logger.startProgress(message, timeout: timeoutConfiguration.slowOperation); 451 try { 452 await _downloadFile(url, tempFile); 453 status.stop(); 454 } catch (exception) { 455 status.cancel(); 456 rethrow; 457 } 458 } else { 459 logger.printTrace('$message (cached)'); 460 } 461 _ensureExists(location); 462 extractor(tempFile, location); 463 }); 464 } 465 466 /// Download a zip archive from the given [url] and unzip it to [location]. 467 Future<void> _downloadZipArchive(String message, Uri url, Directory location) { 468 return _downloadArchive(message, url, location, os.verifyZip, os.unzip); 469 } 470 471 /// Download a gzipped tarball from the given [url] and unpack it to [location]. 472 Future<void> _downloadZippedTarball(String message, Uri url, Directory location) { 473 return _downloadArchive(message, url, location, os.verifyGzip, os.unpack); 474 } 475 476 /// Create a temporary file and invoke [onTemporaryFile] with the file as 477 /// argument, then add the temporary file to the [_downloadedFiles]. 478 Future<void> _withDownloadFile(String name, Future<void> onTemporaryFile(File file)) async { 479 final File tempFile = fs.file(fs.path.join(cache.getDownloadDir().path, name)); 480 _downloadedFiles.add(tempFile); 481 await onTemporaryFile(tempFile); 482 } 483} 484 485bool _hasWarnedAboutStorageOverride = false; 486 487void _maybeWarnAboutStorageOverride(String overrideUrl) { 488 if (_hasWarnedAboutStorageOverride) 489 return; 490 logger.printStatus( 491 'Flutter assets will be downloaded from $overrideUrl. Make sure you trust this source!', 492 emphasis: true, 493 ); 494 _hasWarnedAboutStorageOverride = true; 495} 496 497/// A cached artifact containing fonts used for Material Design. 498class MaterialFonts extends CachedArtifact { 499 MaterialFonts(Cache cache) : super( 500 'material_fonts', 501 cache, 502 const <DevelopmentArtifact>{ DevelopmentArtifact.universal }, 503 ); 504 505 @override 506 Future<void> updateInner() { 507 final Uri archiveUri = _toStorageUri(version); 508 return _downloadZipArchive('Downloading Material fonts...', archiveUri, location); 509 } 510} 511 512/// A cached artifact containing the web dart:ui sources, platform dill files, 513/// and libraries.json. 514/// 515/// This SDK references code within the regular Dart sdk to reduce download size. 516class FlutterWebSdk extends CachedArtifact { 517 FlutterWebSdk(Cache cache) : super( 518 'flutter_web_sdk', 519 cache, 520 const <DevelopmentArtifact>{ DevelopmentArtifact.web }, 521 ); 522 523 @override 524 Directory get location => cache.getWebSdkDirectory(); 525 526 @override 527 String get version => cache.getVersionFor('engine'); 528 529 @override 530 Future<void> updateInner() async { 531 String platformName = 'flutter-web-sdk-'; 532 if (platform.isMacOS) { 533 platformName += 'darwin-x64'; 534 } else if (platform.isLinux) { 535 platformName += 'linux-x64'; 536 } else if (platform.isWindows) { 537 platformName += 'windows-x64'; 538 } 539 final Uri url = Uri.parse('$_storageBaseUrl/flutter_infra/flutter/$version/$platformName.zip'); 540 await _downloadZipArchive('Downloading Web SDK...', url, location); 541 // This is a temporary work-around for not being able to safely download into a shared directory. 542 for (FileSystemEntity entity in location.listSync(recursive: true)) { 543 if (entity is File) { 544 final List<String> segments = fs.path.split(entity.path); 545 segments.remove('flutter_web_sdk'); 546 final String newPath = fs.path.joinAll(segments); 547 final File newFile = fs.file(newPath); 548 if (!newFile.existsSync()) { 549 newFile.createSync(recursive: true); 550 } 551 entity.copySync(newPath); 552 } 553 } 554 } 555} 556 557abstract class EngineCachedArtifact extends CachedArtifact { 558 EngineCachedArtifact( 559 this.stampName, 560 Cache cache, 561 Set<DevelopmentArtifact> requiredArtifacts, 562 ) : super('engine', cache, requiredArtifacts); 563 564 @override 565 final String stampName; 566 567 /// Return a list of (directory path, download URL path) tuples. 568 List<List<String>> getBinaryDirs(); 569 570 /// A list of cache directory paths to which the LICENSE file should be copied. 571 List<String> getLicenseDirs(); 572 573 /// A list of the dart package directories to download. 574 List<String> getPackageDirs(); 575 576 @override 577 bool isUpToDateInner() { 578 final Directory pkgDir = cache.getCacheDir('pkg'); 579 for (String pkgName in getPackageDirs()) { 580 final String pkgPath = fs.path.join(pkgDir.path, pkgName); 581 if (!fs.directory(pkgPath).existsSync()) { 582 return false; 583 } 584 } 585 586 for (List<String> toolsDir in getBinaryDirs()) { 587 final Directory dir = fs.directory(fs.path.join(location.path, toolsDir[0])); 588 if (!dir.existsSync()) { 589 return false; 590 } 591 } 592 593 for (String licenseDir in getLicenseDirs()) { 594 final File file = fs.file(fs.path.join(location.path, licenseDir, 'LICENSE')); 595 if (!file.existsSync()) { 596 return false; 597 } 598 } 599 return true; 600 } 601 602 @override 603 Future<void> updateInner() async { 604 final String url = '$_storageBaseUrl/flutter_infra/flutter/$version/'; 605 606 final Directory pkgDir = cache.getCacheDir('pkg'); 607 for (String pkgName in getPackageDirs()) { 608 await _downloadZipArchive('Downloading package $pkgName...', Uri.parse(url + pkgName + '.zip'), pkgDir); 609 } 610 611 for (List<String> toolsDir in getBinaryDirs()) { 612 final String cacheDir = toolsDir[0]; 613 final String urlPath = toolsDir[1]; 614 final Directory dir = fs.directory(fs.path.join(location.path, cacheDir)); 615 await _downloadZipArchive('Downloading $cacheDir tools...', Uri.parse(url + urlPath), dir); 616 617 _makeFilesExecutable(dir); 618 619 const List<String> frameworkNames = <String>['Flutter', 'FlutterMacOS']; 620 for (String frameworkName in frameworkNames) { 621 final File frameworkZip = fs.file(fs.path.join(dir.path, '$frameworkName.framework.zip')); 622 if (frameworkZip.existsSync()) { 623 final Directory framework = fs.directory(fs.path.join(dir.path, '$frameworkName.framework')); 624 framework.createSync(); 625 os.unzip(frameworkZip, framework); 626 } 627 } 628 } 629 630 final File licenseSource = fs.file(fs.path.join(Cache.flutterRoot, 'LICENSE')); 631 for (String licenseDir in getLicenseDirs()) { 632 final String licenseDestinationPath = fs.path.join(location.path, licenseDir, 'LICENSE'); 633 await licenseSource.copy(licenseDestinationPath); 634 } 635 } 636 637 Future<bool> checkForArtifacts(String engineVersion) async { 638 engineVersion ??= version; 639 final String url = '$_storageBaseUrl/flutter_infra/flutter/$engineVersion/'; 640 641 bool exists = false; 642 for (String pkgName in getPackageDirs()) { 643 exists = await _doesRemoteExist('Checking package $pkgName is available...', 644 Uri.parse(url + pkgName + '.zip')); 645 if (!exists) { 646 return false; 647 } 648 } 649 650 for (List<String> toolsDir in getBinaryDirs()) { 651 final String cacheDir = toolsDir[0]; 652 final String urlPath = toolsDir[1]; 653 exists = await _doesRemoteExist('Checking $cacheDir tools are available...', 654 Uri.parse(url + urlPath)); 655 if (!exists) { 656 return false; 657 } 658 } 659 return true; 660 } 661 662 void _makeFilesExecutable(Directory dir) { 663 os.chmod(dir, 'a+r,a+x'); 664 for (FileSystemEntity entity in dir.listSync(recursive: true)) { 665 if (entity is File) { 666 final FileStat stat = entity.statSync(); 667 final bool isUserExecutable = ((stat.mode >> 6) & 0x1) == 1; 668 if (entity.basename == 'flutter_tester' || isUserExecutable) { 669 // Make the file readable and executable by all users. 670 os.chmod(entity, 'a+r,a+x'); 671 } 672 } 673 } 674 } 675} 676 677 678/// A cached artifact containing the dart:ui source code. 679class FlutterSdk extends EngineCachedArtifact { 680 FlutterSdk(Cache cache) : super( 681 'flutter_sdk', 682 cache, 683 const <DevelopmentArtifact>{ DevelopmentArtifact.universal }, 684 ); 685 686 @override 687 List<String> getPackageDirs() => const <String>['sky_engine']; 688 689 @override 690 List<List<String>> getBinaryDirs() { 691 return <List<String>>[ 692 <String>['common', 'flutter_patched_sdk.zip'], 693 <String>['common', 'flutter_patched_sdk_product.zip'], 694 if (cache.includeAllPlatforms) 695 ...<List<String>>[ 696 <String>['windows-x64', 'windows-x64/artifacts.zip'], 697 <String>['linux-x64', 'linux-x64/artifacts.zip'], 698 <String>['darwin-x64', 'darwin-x64/artifacts.zip'], 699 ] 700 else if (platform.isWindows) 701 <String>['windows-x64', 'windows-x64/artifacts.zip'] 702 else if (platform.isMacOS) 703 <String>['darwin-x64', 'darwin-x64/artifacts.zip'] 704 else if (platform.isLinux) 705 <String>['linux-x64', 'linux-x64/artifacts.zip'], 706 ]; 707 } 708 709 @override 710 List<String> getLicenseDirs() => const <String>[]; 711} 712 713class MacOSEngineArtifacts extends EngineCachedArtifact { 714 MacOSEngineArtifacts(Cache cache) : super( 715 'macos-sdk', 716 cache, 717 const <DevelopmentArtifact> { DevelopmentArtifact.macOS }, 718 ); 719 720 @override 721 List<String> getPackageDirs() => const <String>[]; 722 723 @override 724 List<List<String>> getBinaryDirs() { 725 if (platform.isMacOS) { 726 return _macOSDesktopBinaryDirs; 727 } 728 return const <List<String>>[]; 729 } 730 731 @override 732 List<String> getLicenseDirs() => const <String>[]; 733} 734 735class WindowsEngineArtifacts extends EngineCachedArtifact { 736 WindowsEngineArtifacts(Cache cache) : super( 737 'windows-sdk', 738 cache, 739 const <DevelopmentArtifact> { DevelopmentArtifact.windows }, 740 ); 741 742 @override 743 List<String> getPackageDirs() => const <String>[]; 744 745 @override 746 List<List<String>> getBinaryDirs() { 747 if (platform.isWindows) { 748 return _windowsDesktopBinaryDirs; 749 } 750 return const <List<String>>[]; 751 } 752 753 @override 754 List<String> getLicenseDirs() => const <String>[]; 755} 756 757class LinuxEngineArtifacts extends EngineCachedArtifact { 758 LinuxEngineArtifacts(Cache cache) : super( 759 'linux-sdk', 760 cache, 761 const <DevelopmentArtifact> { DevelopmentArtifact.linux }, 762 ); 763 764 @override 765 List<String> getPackageDirs() => const <String>[]; 766 767 @override 768 List<List<String>> getBinaryDirs() { 769 if (platform.isLinux) { 770 return _linuxDesktopBinaryDirs; 771 } 772 return const <List<String>>[]; 773 } 774 775 @override 776 List<String> getLicenseDirs() => const <String>[]; 777} 778 779class AndroidEngineArtifacts extends EngineCachedArtifact { 780 AndroidEngineArtifacts(Cache cache) : super( 781 'android-sdk', 782 cache, 783 const <DevelopmentArtifact>{ DevelopmentArtifact.android }, 784 ); 785 786 @override 787 List<String> getPackageDirs() => const <String>[]; 788 789 @override 790 List<List<String>> getBinaryDirs() { 791 return <List<String>>[ 792 if (cache.includeAllPlatforms) 793 ...<List<String>>[ 794 ..._osxBinaryDirs, 795 ..._linuxBinaryDirs, 796 ..._windowsBinaryDirs, 797 ..._androidBinaryDirs, 798 ..._dartSdks, 799 ] 800 else if (platform.isWindows) 801 ...<List<String>>[ 802 ..._windowsBinaryDirs, 803 ..._androidBinaryDirs, 804 ] 805 else if (platform.isMacOS) 806 ...<List<String>>[ 807 ..._osxBinaryDirs, 808 ..._androidBinaryDirs, 809 ] 810 else if (platform.isLinux) 811 ...<List<String>>[ 812 ..._linuxBinaryDirs, 813 ..._androidBinaryDirs, 814 ] 815 ]; 816 } 817 818 @override 819 List<String> getLicenseDirs() { return <String>[]; } 820} 821 822class IOSEngineArtifacts extends EngineCachedArtifact { 823 IOSEngineArtifacts(Cache cache) : super( 824 'ios-sdk', 825 cache, 826 <DevelopmentArtifact>{ DevelopmentArtifact.iOS }, 827 ); 828 829 @override 830 List<List<String>> getBinaryDirs() { 831 return <List<String>>[ 832 if (platform.isMacOS || cache.includeAllPlatforms) 833 ..._iosBinaryDirs, 834 ]; 835 } 836 837 @override 838 List<String> getLicenseDirs() { 839 if (cache.includeAllPlatforms || platform.isMacOS) { 840 return const <String>['ios', 'ios-profile', 'ios-release']; 841 } 842 return const <String>[]; 843 } 844 845 @override 846 List<String> getPackageDirs() { 847 return <String>[]; 848 } 849} 850 851/// A cached artifact containing Gradle Wrapper scripts and binaries. 852/// 853/// While this is only required for Android, we need to always download it due 854/// the ensurePlatformSpecificTooling logic. 855class GradleWrapper extends CachedArtifact { 856 GradleWrapper(Cache cache) : super( 857 'gradle_wrapper', 858 cache, 859 const <DevelopmentArtifact>{ DevelopmentArtifact.universal }, 860 ); 861 862 List<String> get _gradleScripts => <String>['gradlew', 'gradlew.bat']; 863 864 String get _gradleWrapper => fs.path.join('gradle', 'wrapper', 'gradle-wrapper.jar'); 865 866 @override 867 Future<void> updateInner() { 868 final Uri archiveUri = _toStorageUri(version); 869 return _downloadZippedTarball('Downloading Gradle Wrapper...', archiveUri, location).then<void>((_) { 870 // Delete property file, allowing templates to provide it. 871 fs.file(fs.path.join(location.path, 'gradle', 'wrapper', 'gradle-wrapper.properties')).deleteSync(); 872 // Remove NOTICE file. Should not be part of the template. 873 fs.file(fs.path.join(location.path, 'NOTICE')).deleteSync(); 874 }); 875 } 876 877 @override 878 bool isUpToDateInner() { 879 final Directory wrapperDir = cache.getCacheDir(fs.path.join('artifacts', 'gradle_wrapper')); 880 if (!fs.directory(wrapperDir).existsSync()) { 881 return false; 882 } 883 for (String scriptName in _gradleScripts) { 884 final File scriptFile = fs.file(fs.path.join(wrapperDir.path, scriptName)); 885 if (!scriptFile.existsSync()) 886 return false; 887 } 888 final File gradleWrapperJar = fs.file(fs.path.join(wrapperDir.path, _gradleWrapper)); 889 if (!gradleWrapperJar.existsSync()) 890 return false; 891 return true; 892 } 893} 894 895 const String _cipdBaseUrl = 896 'https://chrome-infra-packages.appspot.com/dl'; 897 898/// Common functionality for pulling Fuchsia SDKs. 899abstract class _FuchsiaSDKArtifacts extends CachedArtifact { 900 _FuchsiaSDKArtifacts(Cache cache, String platform) 901 :_path = 'fuchsia/sdk/core/$platform-amd64', 902 super('fuchsia-$platform', cache, const <DevelopmentArtifact> { 903 DevelopmentArtifact.fuchsia, 904 }); 905 906 final String _path; 907 908 @override 909 Directory get location => cache.getArtifactDirectory('fuchsia'); 910 911 Future<void> _doUpdate() { 912 final String url = '$_cipdBaseUrl/$_path/+/$version'; 913 return _downloadZipArchive('Downloading package fuchsia SDK...', 914 Uri.parse(url), location); 915 } 916} 917 918/// The pre-built flutter runner for Fuchsia development. 919class FlutterRunnerSDKArtifacts extends CachedArtifact { 920 FlutterRunnerSDKArtifacts(Cache cache) 921 : super('flutter_runner', cache, const <DevelopmentArtifact>{ 922 DevelopmentArtifact.flutterRunner, 923 }); 924 925 @override 926 Directory get location => cache.getArtifactDirectory('flutter_runner'); 927 928 @override 929 String get version => cache.getVersionFor('engine'); 930 931 @override 932 Future<void> updateInner() async { 933 if (!platform.isLinux && !platform.isMacOS) { 934 return Future<void>.value(); 935 } 936 final String url = '$_cipdBaseUrl/flutter/fuchsia/+/git_revision:$version'; 937 await _downloadZipArchive('Downloading package flutter runner...', 938 Uri.parse(url), location); 939 } 940} 941 942/// The Fuchsia core SDK for Linux. 943class LinuxFuchsiaSDKArtifacts extends _FuchsiaSDKArtifacts { 944 LinuxFuchsiaSDKArtifacts(Cache cache) : super(cache, 'linux'); 945 946 @override 947 Future<void> updateInner() { 948 if (!platform.isLinux) { 949 return Future<void>.value(); 950 } 951 return _doUpdate(); 952 } 953} 954 955/// The Fuchsia core SDK for MacOS. 956class MacOSFuchsiaSDKArtifacts extends _FuchsiaSDKArtifacts { 957 MacOSFuchsiaSDKArtifacts(Cache cache) : super(cache, 'mac'); 958 959 @override 960 Future<void> updateInner() async { 961 if (!platform.isMacOS) { 962 return Future<void>.value(); 963 } 964 return _doUpdate(); 965 } 966} 967 968/// Cached iOS/USB binary artifacts. 969class IosUsbArtifacts extends CachedArtifact { 970 IosUsbArtifacts(String name, Cache cache) : super( 971 name, 972 cache, 973 // This is universal to ensure every command checks for them first 974 const <DevelopmentArtifact>{ DevelopmentArtifact.universal }, 975 ); 976 977 static const List<String> artifactNames = <String>[ 978 'libimobiledevice', 979 'usbmuxd', 980 'libplist', 981 'openssl', 982 'ideviceinstaller', 983 'ios-deploy', 984 ]; 985 986 @override 987 String get dyLdLibPath { 988 return cache.getArtifactDirectory(name).path; 989 } 990 991 @override 992 Future<void> updateInner() { 993 if (!platform.isMacOS) { 994 return Future<void>.value(); 995 } 996 final Uri archiveUri = Uri.parse('$_storageBaseUrl/flutter_infra/ios-usb-dependencies/$name/$version/$name.zip'); 997 return _downloadZipArchive('Downloading $name...', archiveUri, location); 998 } 999} 1000 1001// Many characters are problematic in filenames, especially on Windows. 1002final Map<int, List<int>> _flattenNameSubstitutions = <int, List<int>>{ 1003 r'@'.codeUnitAt(0): '@@'.codeUnits, 1004 r'/'.codeUnitAt(0): '@s@'.codeUnits, 1005 r'\'.codeUnitAt(0): '@bs@'.codeUnits, 1006 r':'.codeUnitAt(0): '@c@'.codeUnits, 1007 r'%'.codeUnitAt(0): '@per@'.codeUnits, 1008 r'*'.codeUnitAt(0): '@ast@'.codeUnits, 1009 r'<'.codeUnitAt(0): '@lt@'.codeUnits, 1010 r'>'.codeUnitAt(0): '@gt@'.codeUnits, 1011 r'"'.codeUnitAt(0): '@q@'.codeUnits, 1012 r'|'.codeUnitAt(0): '@pip@'.codeUnits, 1013 r'?'.codeUnitAt(0): '@ques@'.codeUnits, 1014}; 1015 1016/// Given a name containing slashes, colons, and backslashes, expand it into 1017/// something that doesn't. 1018String _flattenNameNoSubdirs(String fileName) { 1019 final List<int> replacedCodeUnits = <int>[ 1020 for (int codeUnit in fileName.codeUnits) 1021 ..._flattenNameSubstitutions[codeUnit] ?? <int>[codeUnit], 1022 ]; 1023 return String.fromCharCodes(replacedCodeUnits); 1024} 1025 1026@visibleForTesting 1027String flattenNameSubdirs(Uri url) { 1028 final List<String> pieces = <String>[url.host, ...url.pathSegments]; 1029 final Iterable<String> convertedPieces = pieces.map<String>(_flattenNameNoSubdirs); 1030 return fs.path.joinAll(convertedPieces); 1031} 1032 1033/// Download a file from the given [url] and write it to [location]. 1034Future<void> _downloadFile(Uri url, File location) async { 1035 _ensureExists(location.parent); 1036 final List<int> fileBytes = await fetchUrl(url); 1037 location.writeAsBytesSync(fileBytes, flush: true); 1038} 1039 1040Future<bool> _doesRemoteExist(String message, Uri url) async { 1041 final Status status = logger.startProgress(message, timeout: timeoutConfiguration.slowOperation); 1042 final bool exists = await doesRemoteFileExist(url); 1043 status.stop(); 1044 return exists; 1045} 1046 1047/// Create the given [directory] and parents, as necessary. 1048void _ensureExists(Directory directory) { 1049 if (!directory.existsSync()) { 1050 directory.createSync(recursive: true); 1051 } 1052} 1053 1054const List<List<String>> _windowsDesktopBinaryDirs = <List<String>>[ 1055 <String>['windows-x64', 'windows-x64/windows-x64-flutter.zip'], 1056 <String>['windows-x64', 'windows-x64/flutter-cpp-client-wrapper.zip'], 1057]; 1058 1059const List<List<String>> _linuxDesktopBinaryDirs = <List<String>>[ 1060 <String>['linux-x64', 'linux-x64/linux-x64-flutter.zip'], 1061 <String>['linux-x64', 'linux-x64/flutter-cpp-client-wrapper.zip'], 1062]; 1063 1064const List<List<String>> _macOSDesktopBinaryDirs = <List<String>>[ 1065 <String>['darwin-x64', 'darwin-x64/FlutterMacOS.framework.zip'], 1066]; 1067 1068const List<List<String>> _osxBinaryDirs = <List<String>>[ 1069 <String>['android-arm-profile/darwin-x64', 'android-arm-profile/darwin-x64.zip'], 1070 <String>['android-arm-release/darwin-x64', 'android-arm-release/darwin-x64.zip'], 1071 <String>['android-arm64-profile/darwin-x64', 'android-arm64-profile/darwin-x64.zip'], 1072 <String>['android-arm64-release/darwin-x64', 'android-arm64-release/darwin-x64.zip'], 1073]; 1074 1075const List<List<String>> _linuxBinaryDirs = <List<String>>[ 1076 <String>['android-arm-profile/linux-x64', 'android-arm-profile/linux-x64.zip'], 1077 <String>['android-arm-release/linux-x64', 'android-arm-release/linux-x64.zip'], 1078 <String>['android-arm64-profile/linux-x64', 'android-arm64-profile/linux-x64.zip'], 1079 <String>['android-arm64-release/linux-x64', 'android-arm64-release/linux-x64.zip'], 1080]; 1081 1082const List<List<String>> _windowsBinaryDirs = <List<String>>[ 1083 <String>['android-arm-profile/windows-x64', 'android-arm-profile/windows-x64.zip'], 1084 <String>['android-arm-release/windows-x64', 'android-arm-release/windows-x64.zip'], 1085 <String>['android-arm64-profile/windows-x64', 'android-arm64-profile/windows-x64.zip'], 1086 <String>['android-arm64-release/windows-x64', 'android-arm64-release/windows-x64.zip'], 1087]; 1088 1089const List<List<String>> _androidBinaryDirs = <List<String>>[ 1090 <String>['android-x86', 'android-x86/artifacts.zip'], 1091 <String>['android-x64', 'android-x64/artifacts.zip'], 1092 <String>['android-arm', 'android-arm/artifacts.zip'], 1093 <String>['android-arm-profile', 'android-arm-profile/artifacts.zip'], 1094 <String>['android-arm-release', 'android-arm-release/artifacts.zip'], 1095 <String>['android-arm64', 'android-arm64/artifacts.zip'], 1096 <String>['android-arm64-profile', 'android-arm64-profile/artifacts.zip'], 1097 <String>['android-arm64-release', 'android-arm64-release/artifacts.zip'], 1098]; 1099 1100const List<List<String>> _iosBinaryDirs = <List<String>>[ 1101 <String>['ios', 'ios/artifacts.zip'], 1102 <String>['ios-profile', 'ios-profile/artifacts.zip'], 1103 <String>['ios-release', 'ios-release/artifacts.zip'], 1104]; 1105 1106const List<List<String>> _dartSdks = <List<String>> [ 1107 <String>['darwin-x64', 'dart-sdk-darwin-x64.zip'], 1108 <String>['linux-x64', 'dart-sdk-linux-x64.zip'], 1109 <String>['windows-x64', 'dart-sdk-windows-x64.zip'], 1110]; 1111