diff --git a/packages/flutter_tools/bin/fuchsia_attach.dart b/packages/flutter_tools/bin/fuchsia_attach.dart index 0180b73f92..a8bba44ef9 100644 --- a/packages/flutter_tools/bin/fuchsia_attach.dart +++ b/packages/flutter_tools/bin/fuchsia_attach.dart @@ -134,4 +134,7 @@ class _FuchsiaAttachCommand extends AttachCommand { Cache.flutterRoot = '$originalWorkingDirectory/third_party/dart-pkg/git/flutter'; return super.runCommand(); } + + @override + Future updateCache() async {} } diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh index 0537672b23..5144f9f04c 100755 --- a/packages/flutter_tools/bin/xcode_backend.sh +++ b/packages/flutter_tools/bin/xcode_backend.sh @@ -84,7 +84,6 @@ BuildApp() { local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}" - AssertExists "${framework_path}" AssertExists "${project_path}" local derived_dir="${SOURCE_ROOT}/Flutter" @@ -118,6 +117,12 @@ BuildApp() { flutter_podspec="${LOCAL_ENGINE}/Flutter.podspec" fi + # If the framework path does not exist, ensure that it is downloaded. + if [[ ! -e "$1" ]]; then + FLUTTER_ALREADY_LOCKED = "true" + RunCommand "${FLUTTER_ROOT}/bin/flutter" precache --suppress-analytics + fi + if [[ -e "${project_path}/.ios" ]]; then RunCommand rm -rf -- "${derived_dir}/engine" mkdir "${derived_dir}/engine" diff --git a/packages/flutter_tools/lib/src/base/net.dart b/packages/flutter_tools/lib/src/base/net.dart index 5196fc796a..54a71f0be0 100644 --- a/packages/flutter_tools/lib/src/base/net.dart +++ b/packages/flutter_tools/lib/src/base/net.dart @@ -37,7 +37,8 @@ Future> _attempt(Uri url, {bool onlyHeaders = false}) async { printTrace('Downloading: $url'); HttpClient httpClient; if (context[HttpClientFactory] != null) { - httpClient = (context[HttpClientFactory] as HttpClientFactory)(); // ignore: avoid_as + final HttpClientFactory httpClientFactory = context[HttpClientFactory]; + httpClient = httpClientFactory(); } else { httpClient = HttpClient(); } @@ -64,9 +65,9 @@ Future> _attempt(Uri url, {bool onlyHeaders = false}) async { // If we're making a HEAD request, we're only checking to see if the URL is // valid. if (onlyHeaders) { - return (response.statusCode == 200) ? [] : null; + return (response.statusCode == HttpStatus.ok) ? [] : null; } - if (response.statusCode != 200) { + if (response.statusCode != HttpStatus.ok) { if (response.statusCode > 0 && response.statusCode < 500) { throwToolExit( 'Download failed.\n' diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index fe5089ecb8..a497ca3da0 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart @@ -13,6 +13,7 @@ import 'base/logger.dart'; import 'base/net.dart'; import 'base/os.dart'; import 'base/platform.dart'; +import 'build_info.dart'; import 'globals.dart'; /// A wrapper around the `bin/cache/` directory. @@ -187,7 +188,24 @@ class Cache { return isOlderThanReference(entity: entity, referenceFile: flutterToolsStamp); } - bool isUpToDate() => _artifacts.every((CachedArtifact artifact) => artifact.isUpToDate()); + UpdateResult isUpToDate({ + List buildModes = const [], + List targetPlatforms = const [], + bool skipUnknown = true, + }) { + bool isUpToDate = true; + bool clobber = false; + for (CachedArtifact artifact in _artifacts) { + final UpdateResult result = artifact.isUpToDate( + buildModes: buildModes, + targetPlatforms: targetPlatforms, + skipUnknown: skipUnknown, + ); + isUpToDate &= result.isUpToDate; + clobber |= result.clobber; + } + return UpdateResult(isUpToDate: isUpToDate, clobber: clobber); + } Future getThirdPartyFile(String urlStr, String serviceName) async { final Uri url = Uri.parse(urlStr); @@ -210,13 +228,40 @@ class Cache { return cachedFile.path; } - Future updateAll() async { - if (!_lockEnabled) + Future updateAll({ + List buildModes = const [], + List targetPlatforms = const [], + bool skipUnknown = true, + bool clobber = false, + }) async { + if (!_lockEnabled) { return; + } try { for (CachedArtifact artifact in _artifacts) { - if (!artifact.isUpToDate()) - await artifact.update(); + bool localClobber = clobber; + if (localClobber) { + await artifact.update( + buildModes: buildModes, + targetPlatforms: targetPlatforms, + skipUnknown: skipUnknown, + clobber: localClobber, + ); + } + final UpdateResult result = artifact.isUpToDate( + buildModes: buildModes, + targetPlatforms: targetPlatforms, + skipUnknown: skipUnknown, + ); + localClobber |= result.clobber; + if (localClobber || !result.isUpToDate) { + await artifact.update( + buildModes: buildModes, + targetPlatforms: targetPlatforms, + skipUnknown: skipUnknown, + clobber: localClobber, + ); + } } } on SocketException catch (e) { if (_hostsBlockedInChina.contains(e.address?.host)) { @@ -232,6 +277,15 @@ class Cache { } } +class UpdateResult { + const UpdateResult({this.isUpToDate, this.clobber = false}); + + /// Whether the artifact exists and is the correct version. + final bool isUpToDate; + /// Whether the artifact needs to be redownloaded. + final bool clobber; +} + /// An artifact managed by the cache. abstract class CachedArtifact { CachedArtifact(this.name, this.cache); @@ -248,19 +302,39 @@ abstract class CachedArtifact { /// starting from scratch. final List _downloadedFiles = []; - bool isUpToDate() { - if (!location.existsSync()) - return false; - if (version != cache.getStampFor(name)) - return false; - return isUpToDateInner(); + @mustCallSuper + UpdateResult isUpToDate({ + List buildModes = const [], + List targetPlatforms = const [], + bool skipUnknown = true, + }) { + if (!location.existsSync()) { + return const UpdateResult(isUpToDate: false, clobber: false); + } + if (version != cache.getStampFor(name)) { + return const UpdateResult(isUpToDate: false, clobber: true); + } + return const UpdateResult(isUpToDate: true, clobber: false); } - Future update() async { - if (location.existsSync()) + Future update({ + List buildModes, + List targetPlatforms, + bool skipUnknown = true, + bool clobber = false, + }) async { + if (location.existsSync() && clobber) { location.deleteSync(recursive: true); - location.createSync(recursive: true); - await updateInner(); + } + if (!location.existsSync()) { + location.createSync(recursive: true); + } + await updateInner( + buildModes: buildModes, + targetPlatforms: targetPlatforms, + skipUnknown: skipUnknown, + clobber: clobber, + ); cache.setStampFor(name, version); _removeDownloadedFiles(); } @@ -268,6 +342,9 @@ abstract class CachedArtifact { /// Clear any zip/gzip files downloaded. void _removeDownloadedFiles() { for (File f in _downloadedFiles) { + if (!f.existsSync()) { + continue; + } f.deleteSync(); for (Directory d = f.parent; d.absolute.path != cache.getDownloadDir().absolute.path; d = d.parent) { if (d.listSync().isEmpty) { @@ -279,11 +356,13 @@ abstract class CachedArtifact { } } - /// Hook method for extra checks for being up-to-date. - bool isUpToDateInner() => true; - /// Template method to perform artifact update. - Future updateInner(); + Future updateInner({ + @required List buildModes, + @required List targetPlatforms, + @required bool skipUnknown, + @required bool clobber, + }); String get _storageBaseUrl { final String overrideUrl = platform.environment['FLUTTER_STORAGE_BASE_URL']; @@ -351,9 +430,16 @@ class MaterialFonts extends CachedArtifact { MaterialFonts(Cache cache) : super('material_fonts', cache); @override - Future updateInner() { + Future updateInner({ + List buildModes, + List targetPlatforms, + bool skipUnknown, + bool clobber, + }) async { final Uri archiveUri = _toStorageUri(version); - return _downloadZipArchive('Downloading Material fonts...', archiveUri, location); + if (fs.directory(location).listSync().isEmpty || clobber) { + await _downloadZipArchive('Downloading Material fonts...', archiveUri, location); + } } } @@ -361,100 +447,387 @@ class MaterialFonts extends CachedArtifact { class FlutterEngine extends CachedArtifact { FlutterEngine(Cache cache) : super('engine', cache); - List _getPackageDirs() => const ['sky_engine']; - - // Return a list of (cache directory path, download URL path) tuples. - List> _getBinaryDirs() { - final List> binaryDirs = >[]; - - binaryDirs.add(['common', 'flutter_patched_sdk.zip']); - - if (cache.includeAllPlatforms) - binaryDirs - ..addAll(_osxBinaryDirs) - ..addAll(_linuxBinaryDirs) - ..addAll(_windowsBinaryDirs) - ..addAll(_androidBinaryDirs) - ..addAll(_iosBinaryDirs) - ..addAll(_dartSdks); - else if (platform.isLinux) - binaryDirs - ..addAll(_linuxBinaryDirs) - ..addAll(_androidBinaryDirs); - else if (platform.isMacOS) - binaryDirs - ..addAll(_osxBinaryDirs) - ..addAll(_androidBinaryDirs) - ..addAll(_iosBinaryDirs); - else if (platform.isWindows) - binaryDirs - ..addAll(_windowsBinaryDirs) - ..addAll(_androidBinaryDirs); - - return binaryDirs; + // Return a list of [BinaryArtifact]s to download. + @visibleForTesting + List getBinaryDirs({ + @required List buildModes, + @required List targetPlatforms, + @required bool skipUnknown, + }) { + TargetPlatform hostPlatform; + if (cache.includeAllPlatforms) { + hostPlatform = null; + } if (platform.isMacOS) { + hostPlatform = TargetPlatform.darwin_x64; + } else if (platform.isLinux) { + hostPlatform = TargetPlatform.linux_x64; + } else if (platform.isWindows) { + hostPlatform = TargetPlatform.windows_x64; + } + final List results = _reduceEngineBinaries( + buildModes: buildModes, + targetPlatforms: targetPlatforms, + hostPlatform: hostPlatform, + skipUnknown: skipUnknown, + ).toList(); + if (cache.includeAllPlatforms) { + return results + _dartSdks; + } + return results; } - List> get _osxBinaryDirs => >[ - ['darwin-x64', 'darwin-x64/artifacts.zip'], - ['android-arm-profile/darwin-x64', 'android-arm-profile/darwin-x64.zip'], - ['android-arm-release/darwin-x64', 'android-arm-release/darwin-x64.zip'], - ['android-arm64-profile/darwin-x64', 'android-arm64-profile/darwin-x64.zip'], - ['android-arm64-release/darwin-x64', 'android-arm64-release/darwin-x64.zip'], - ['android-arm-dynamic-profile/darwin-x64', 'android-arm-dynamic-profile/darwin-x64.zip'], - ['android-arm-dynamic-release/darwin-x64', 'android-arm-dynamic-release/darwin-x64.zip'], - ['android-arm64-dynamic-profile/darwin-x64', 'android-arm64-dynamic-profile/darwin-x64.zip'], - ['android-arm64-dynamic-release/darwin-x64', 'android-arm64-dynamic-release/darwin-x64.zip'], + Iterable _reduceEngineBinaries({ + List buildModes, + List targetPlatforms, + TargetPlatform hostPlatform, + bool skipUnknown, + }) { + return _binaries.where((BinaryArtifact engineBinary) { + if (hostPlatform != null && engineBinary.hostPlatform != null && engineBinary.hostPlatform != hostPlatform) { + return false; + } + // match if artifact has no restrictions. + if (engineBinary.skipChecks || engineBinary.buildMode == null && engineBinary.targetPlatform == null) { + return true; + } + final bool buildModeMatch = buildModes.any((BuildMode buildMode) => buildMode == engineBinary.buildMode); + final bool targetPlatformMatch = targetPlatforms.any((TargetPlatform targetPlatform) => targetPlatform == engineBinary.targetPlatform); + if (buildModeMatch && targetPlatformMatch // match if artifact exactly matches requiremnets. + || skipUnknown && buildModeMatch && targetPlatforms.isEmpty // match if build mode matches but target platform is unknown. + || skipUnknown && targetPlatformMatch && buildModes.isEmpty // match if target platform matches but build mode is null. + || !skipUnknown && targetPlatforms.isEmpty && buildModes.isEmpty) { // match if neither are provided but skipUnknown flag is provided. + return true; + } + return false; + }); + } + + List get _packages => const [ + BinaryArtifact( + name: 'sky_engine', + fileName: 'sky_engine' + ), ]; - List> get _linuxBinaryDirs => >[ - ['linux-x64', 'linux-x64/artifacts.zip'], - ['android-arm-profile/linux-x64', 'android-arm-profile/linux-x64.zip'], - ['android-arm-release/linux-x64', 'android-arm-release/linux-x64.zip'], - ['android-arm64-profile/linux-x64', 'android-arm64-profile/linux-x64.zip'], - ['android-arm64-release/linux-x64', 'android-arm64-release/linux-x64.zip'], - ['android-arm-dynamic-profile/linux-x64', 'android-arm-dynamic-profile/linux-x64.zip'], - ['android-arm-dynamic-release/linux-x64', 'android-arm-dynamic-release/linux-x64.zip'], - ['android-arm64-dynamic-profile/linux-x64', 'android-arm64-dynamic-profile/linux-x64.zip'], - ['android-arm64-dynamic-release/linux-x64', 'android-arm64-dynamic-release/linux-x64.zip'], + /// This lives separately since we only download it when includeAllPlatforms is true. + List get _dartSdks => const [ + BinaryArtifact( + name: 'darwin-x64', + fileName: 'dart-sdk-darwin-x64.zip', + ), + BinaryArtifact( + name: 'linux-x64', + fileName: 'dart-sdk-linux-x64.zip', + ), + BinaryArtifact( + name: 'windows-x64', + fileName: 'dart-sdk-windows-x64.zip', + ), ]; - List> get _windowsBinaryDirs => >[ - ['windows-x64', 'windows-x64/artifacts.zip'], - ['android-arm-profile/windows-x64', 'android-arm-profile/windows-x64.zip'], - ['android-arm-release/windows-x64', 'android-arm-release/windows-x64.zip'], - ['android-arm64-profile/windows-x64', 'android-arm64-profile/windows-x64.zip'], - ['android-arm64-release/windows-x64', 'android-arm64-release/windows-x64.zip'], - ['android-arm-dynamic-profile/windows-x64', 'android-arm-dynamic-profile/windows-x64.zip'], - ['android-arm-dynamic-release/windows-x64', 'android-arm-dynamic-release/windows-x64.zip'], - ['android-arm64-dynamic-profile/windows-x64', 'android-arm64-dynamic-profile/windows-x64.zip'], - ['android-arm64-dynamic-release/windows-x64', 'android-arm64-dynamic-release/windows-x64.zip'], - ]; - - List> get _androidBinaryDirs => >[ - ['android-x86', 'android-x86/artifacts.zip'], - ['android-x64', 'android-x64/artifacts.zip'], - ['android-arm', 'android-arm/artifacts.zip'], - ['android-arm-profile', 'android-arm-profile/artifacts.zip'], - ['android-arm-release', 'android-arm-release/artifacts.zip'], - ['android-arm64', 'android-arm64/artifacts.zip'], - ['android-arm64-profile', 'android-arm64-profile/artifacts.zip'], - ['android-arm64-release', 'android-arm64-release/artifacts.zip'], - ['android-arm-dynamic-profile', 'android-arm-dynamic-profile/artifacts.zip'], - ['android-arm-dynamic-release', 'android-arm-dynamic-release/artifacts.zip'], - ['android-arm64-dynamic-profile', 'android-arm64-dynamic-profile/artifacts.zip'], - ['android-arm64-dynamic-release', 'android-arm64-dynamic-release/artifacts.zip'], - ]; - - List> get _iosBinaryDirs => >[ - ['ios', 'ios/artifacts.zip'], - ['ios-profile', 'ios-profile/artifacts.zip'], - ['ios-release', 'ios-release/artifacts.zip'], - ]; - - List> get _dartSdks => > [ - ['darwin-x64', 'dart-sdk-darwin-x64.zip'], - ['linux-x64', 'dart-sdk-linux-x64.zip'], - ['windows-x64', 'dart-sdk-windows-x64.zip'], + /// A set of all possible artifacts to download. + /// + /// Adding a new artifact: + /// + /// To ensure that we do not waste a user's time/data/storage, the flutter + /// tool should only download binaries when they are required. These can be requested + /// in [FlutterCommand.updateCache]. + /// + /// An artifact should have the following features to prevent unecessary download: + /// + /// * `hostPlatform` should be one of `TargetPlatform.linux_x64`, + /// `TargetPlatform.darwin_x64`, or `TargetPlatfrom.windows_x64`. In the + /// case where there is no restriction it can be left as null. + /// * `buildMode` should be one of `BuildMode.debug`, `BuildMode.profile`, + /// `BuildMode.release`, `BuildMode.dynamicRelease`, or + /// `BuildMode.dynamicProfile`. In the case where it is required regardless + /// of buildMode, it can be left null. + /// * `targetPlatform` should be one of the supported target platforms. + /// * If, despite the restrictions above, the artifact should still be + /// downloaded, `skipChecks` can be set to true. + List get _binaries => const [ + BinaryArtifact( + name: 'common', + fileName: 'flutter_patched_sdk.zip', + ), + BinaryArtifact( + name: 'linux-x64', + fileName: 'linux-x64/artifacts.zip', + hostPlatform: TargetPlatform.linux_x64, + ), + BinaryArtifact( + name: 'android-arm-profile/linux-x64', + fileName: 'android-arm-profile/linux-x64.zip', + targetPlatform: TargetPlatform.android_arm, + buildMode: BuildMode.profile, + hostPlatform: TargetPlatform.linux_x64, + skipChecks: true, + ), + BinaryArtifact( + name: 'android-arm-release/linux-x64', + fileName: 'android-arm-release/linux-x64.zip', + targetPlatform: TargetPlatform.android_arm, + buildMode: BuildMode.release, + hostPlatform: TargetPlatform.linux_x64, + ), + BinaryArtifact( + name: 'android-arm64-profile/linux-x64', + fileName: 'android-arm64-profile/linux-x64.zip', + targetPlatform: TargetPlatform.android_arm64, + buildMode: BuildMode.profile, + hostPlatform: TargetPlatform.linux_x64, + ), + BinaryArtifact( + name: 'android-arm64-release/linux-x64', + fileName: 'android-arm64-release/linux-x64.zip', + targetPlatform: TargetPlatform.android_arm64, + buildMode: BuildMode.release, + hostPlatform: TargetPlatform.linux_x64, + ), + BinaryArtifact( + name: 'android-arm-dynamic-profile/linux-x64', + fileName: 'android-arm-dynamic-profile/linux-x64.zip', + targetPlatform: TargetPlatform.android_arm, + buildMode: BuildMode.dynamicProfile, + hostPlatform: TargetPlatform.linux_x64, + ), + BinaryArtifact( + name: 'android-arm-dynamic-release/linux-x64', + fileName: 'android-arm-dynamic-release/linux-x64.zip', + targetPlatform: TargetPlatform.android_arm, + buildMode: BuildMode.dynamicRelease, + hostPlatform: TargetPlatform.linux_x64, + ), + BinaryArtifact( + name: 'android-arm64-dynamic-profile/linux-x64', + fileName: 'android-arm64-dynamic-profile/linux-x64.zip', + targetPlatform: TargetPlatform.android_arm64, + buildMode: BuildMode.dynamicProfile, + hostPlatform: TargetPlatform.linux_x64, + ), + BinaryArtifact( + name: 'android-arm64-dynamic-release/linux-x64', + fileName: 'android-arm64-dynamic-release/linux-x64.zip', + targetPlatform: TargetPlatform.android_arm64, + buildMode: BuildMode.dynamicRelease, + hostPlatform: TargetPlatform.linux_x64, + ), + BinaryArtifact( + name: 'windows-x64', + fileName: 'windows-x64/artifacts.zip', + hostPlatform: TargetPlatform.windows_x64, + ), + BinaryArtifact( + name: 'android-arm-profile/windows-x64', + fileName: 'android-arm-profile/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + targetPlatform: TargetPlatform.android_arm, + buildMode: BuildMode.profile, + skipChecks: true + ), + BinaryArtifact( + name: 'android-arm-release/windows-x64', + fileName: 'android-arm-release/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + targetPlatform: TargetPlatform.android_arm, + buildMode: BuildMode.release, + ), + BinaryArtifact( + name: 'android-arm64-profile/windows-x64', + fileName: 'android-arm64-profile/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + targetPlatform: TargetPlatform.android_arm64, + buildMode: BuildMode.profile, + ), + BinaryArtifact( + name: 'android-arm64-release/windows-x64', + fileName: 'android-arm64-release/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + targetPlatform: TargetPlatform.android_arm64, + buildMode: BuildMode.release, + ), + BinaryArtifact( + name: 'android-arm-dynamic-profile/windows-x64', + fileName: 'android-arm-dynamic-profile/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + targetPlatform: TargetPlatform.android_arm, + buildMode: BuildMode.dynamicProfile, + ), + BinaryArtifact( + name: 'android-arm-dynamic-release/windows-x64', + fileName: 'android-arm-dynamic-release/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + targetPlatform: TargetPlatform.android_arm, + buildMode: BuildMode.dynamicRelease, + ), + BinaryArtifact( + name: 'android-arm64-dynamic-profile/windows-x64', + fileName: 'android-arm64-dynamic-profile/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + targetPlatform: TargetPlatform.android_arm64, + buildMode: BuildMode.dynamicProfile, + ), + BinaryArtifact( + name: 'android-arm64-dynamic-release/windows-x64', + fileName: 'android-arm64-dynamic-release/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + targetPlatform: TargetPlatform.android_arm64, + buildMode: BuildMode.dynamicRelease, + ), + BinaryArtifact( + name: 'android-x86', + fileName: 'android-x86/artifacts.zip', + buildMode: BuildMode.debug, + targetPlatform: TargetPlatform.android_x86, + ), + BinaryArtifact( + name: 'android-x64', + fileName: 'android-x64/artifacts.zip', + buildMode: BuildMode.debug, + targetPlatform: TargetPlatform.android_x64, + ), + BinaryArtifact( + name: 'android-arm', + fileName: 'android-arm/artifacts.zip', + buildMode: BuildMode.debug, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm-profile', + fileName: 'android-arm-profile/artifacts.zip', + buildMode: BuildMode.profile, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm-release', + fileName: 'android-arm-release/artifacts.zip', + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm64', + fileName: 'android-arm64/artifacts.zip', + buildMode: BuildMode.debug, + targetPlatform: TargetPlatform.android_arm64, + ), + BinaryArtifact( + name: 'android-arm64-profile', + fileName: 'android-arm64-profile/artifacts.zip', + buildMode: BuildMode.profile, + targetPlatform: TargetPlatform.android_arm64, + ), + BinaryArtifact( + name: 'android-arm64-release', + fileName: 'android-arm64-release/artifacts.zip', + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm64, + ), + BinaryArtifact( + name: 'android-arm-dynamic-profile', + fileName: 'android-arm-dynamic-profile/artifacts.zip', + buildMode: BuildMode.dynamicProfile, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm-dynamic-release', + fileName: 'android-arm-dynamic-release/artifacts.zip', + buildMode: BuildMode.dynamicRelease, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm64-dynamic-profile', + fileName: 'android-arm64-dynamic-profile/artifacts.zip', + buildMode: BuildMode.dynamicProfile, + targetPlatform: TargetPlatform.android_arm64, + ), + BinaryArtifact( + name: 'android-arm64-dynamic-release', + fileName: 'android-arm64-dynamic-release/artifacts.zip', + buildMode: BuildMode.dynamicRelease, + targetPlatform: TargetPlatform.android_arm64, + ), + BinaryArtifact( + name: 'ios', fileName: 'ios/artifacts.zip', + buildMode: BuildMode.debug, + hostPlatform: TargetPlatform.darwin_x64, + targetPlatform: TargetPlatform.ios, + ), + BinaryArtifact( + name: 'ios-profile', + fileName: 'ios-profile/artifacts.zip', + buildMode: BuildMode.profile, + hostPlatform: TargetPlatform.darwin_x64, + targetPlatform: TargetPlatform.ios, + ), + BinaryArtifact( + name: 'ios-release', + fileName: 'ios-release/artifacts.zip', + buildMode: BuildMode.release, + hostPlatform: TargetPlatform.darwin_x64, + targetPlatform: TargetPlatform.ios, + ), + BinaryArtifact( + name: 'darwin-x64', + fileName: 'darwin-x64/artifacts.zip', + hostPlatform: TargetPlatform.darwin_x64, + ), + BinaryArtifact( + name: 'android-arm-profile/darwin-x64', + fileName: 'android-arm-profile/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.profile, + targetPlatform: TargetPlatform.android_arm, + skipChecks: true, + ), + BinaryArtifact( + name: 'android-arm-release/darwin-x64', + fileName: 'android-arm-release/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm64-profile/darwin-x64', + fileName: 'android-arm64-profile/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.profile, + targetPlatform: TargetPlatform.android_arm64, + ), + BinaryArtifact( + name: 'android-arm64-release/darwin-x64', + fileName: 'android-arm64-release/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm64, + ), + BinaryArtifact( + name: 'android-arm-dynamic-profile/darwin-x64', + fileName: 'android-arm-dynamic-profile/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.dynamicProfile, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm-dynamic-release/darwin-x64', + fileName: 'android-arm-dynamic-release/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.dynamicRelease, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm64-dynamic-profile/darwin-x64', + fileName: 'android-arm64-dynamic-profile/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.dynamicProfile, + targetPlatform: TargetPlatform.android_arm64, + ), + BinaryArtifact( + name: 'android-arm64-dynamic-release/darwin-x64', + fileName: 'android-arm64-dynamic-release/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.dynamicRelease, + targetPlatform: TargetPlatform.android_arm64, + ), ]; // A list of cache directory paths to which the LICENSE file should be copied. @@ -466,52 +839,71 @@ class FlutterEngine extends CachedArtifact { } @override - bool isUpToDateInner() { + UpdateResult isUpToDate({ + List buildModes = const [], + List targetPlatforms = const [], + bool skipUnknown = true, + }) { + final UpdateResult parentResult = super.isUpToDate( + buildModes: buildModes, + targetPlatforms: targetPlatforms, + skipUnknown: skipUnknown + ); + if (!parentResult.isUpToDate || parentResult.clobber) { + return parentResult; + } final Directory pkgDir = cache.getCacheDir('pkg'); - for (String pkgName in _getPackageDirs()) { - final String pkgPath = fs.path.join(pkgDir.path, pkgName); - if (!fs.directory(pkgPath).existsSync()) - return false; + for (BinaryArtifact packageArtifact in _packages) { + final Directory packageDirectory = packageArtifact.artifactLocation(pkgDir); + if (!packageDirectory.existsSync()) { + return const UpdateResult(isUpToDate: false); + } } - - for (List toolsDir in _getBinaryDirs()) { - final Directory dir = fs.directory(fs.path.join(location.path, toolsDir[0])); - if (!dir.existsSync()) - return false; + for (BinaryArtifact toolsArtifact in getBinaryDirs(buildModes: buildModes, targetPlatforms: targetPlatforms, skipUnknown: skipUnknown)) { + final Directory dir = toolsArtifact.artifactLocation(location); + if (!dir.existsSync()) { + return const UpdateResult(isUpToDate: false); + } } - - for (String licenseDir in _getLicenseDirs()) { - final File file = fs.file(fs.path.join(location.path, licenseDir, 'LICENSE')); - if (!file.existsSync()) - return false; - } - return true; + return const UpdateResult(isUpToDate: true); } @override - Future updateInner() async { + Future updateInner({ + @required List buildModes, + @required List targetPlatforms, + @required bool skipUnknown, + @required bool clobber, + }) async { final String url = '$_storageBaseUrl/flutter_infra/flutter/$version/'; - - final Directory pkgDir = cache.getCacheDir('pkg'); - for (String pkgName in _getPackageDirs()) { - final String pkgPath = fs.path.join(pkgDir.path, pkgName); + final Directory packageDirectory = cache.getCacheDir('pkg'); + for (BinaryArtifact rawArtifact in _packages) { + final String pkgPath = fs.path.join(packageDirectory.path, rawArtifact.name); final Directory dir = fs.directory(pkgPath); - if (dir.existsSync()) + final bool exists = dir.existsSync(); + if (exists) { + if (!clobber) { + continue; + } dir.deleteSync(recursive: true); - await _downloadZipArchive('Downloading package $pkgName...', Uri.parse(url + pkgName + '.zip'), pkgDir); + } + final Uri uri = Uri.parse('$url${rawArtifact.name}.zip'); + await _downloadZipArchive('Downloading package ${rawArtifact.name}...', uri, packageDirectory); } - for (List toolsDir in _getBinaryDirs()) { - final String cacheDir = toolsDir[0]; - final String urlPath = toolsDir[1]; - final Directory dir = fs.directory(fs.path.join(location.path, cacheDir)); - await _downloadZipArchive('Downloading $cacheDir tools...', Uri.parse(url + urlPath), dir); + final List rawArtifacts = getBinaryDirs(buildModes: buildModes, targetPlatforms: targetPlatforms, skipUnknown: skipUnknown); + for (BinaryArtifact rawArtifact in rawArtifacts) { + final Directory artifactDirectory = rawArtifact.artifactLocation(location); + if (artifactDirectory.existsSync() && !clobber) { + continue; + } + final Uri uri = rawArtifact.artifactRemoteLocation(url); + await _downloadZipArchive('Downloading ${rawArtifact.name} tools...', uri, artifactDirectory); + _makeFilesExecutable(artifactDirectory); - _makeFilesExecutable(dir); - - final File frameworkZip = fs.file(fs.path.join(dir.path, 'Flutter.framework.zip')); + final File frameworkZip = fs.file(fs.path.join(artifactDirectory.path, 'Flutter.framework.zip')); if (frameworkZip.existsSync()) { - final Directory framework = fs.directory(fs.path.join(dir.path, 'Flutter.framework')); + final Directory framework = fs.directory(fs.path.join(artifactDirectory.path, 'Flutter.framework')); framework.createSync(); os.unzip(frameworkZip, framework); } @@ -520,43 +912,43 @@ class FlutterEngine extends CachedArtifact { final File licenseSource = fs.file(fs.path.join(Cache.flutterRoot, 'LICENSE')); for (String licenseDir in _getLicenseDirs()) { final String licenseDestinationPath = fs.path.join(location.path, licenseDir, 'LICENSE'); + // If the destination does not exist, we did not download the artifact to + // perform this operation. + if (!fs.directory(fs.path.join(location.path, licenseDir)).existsSync()) { + continue; + } await licenseSource.copy(licenseDestinationPath); } } - Future areRemoteArtifactsAvailable({String engineVersion, - bool includeAllPlatforms = true}) async { - final bool includeAllPlatformsState = cache.includeAllPlatforms; + // Checks whether the remote artifacts for `engineVersion` are availible in storage. + Future areRemoteArtifactsAvailable({ + String engineVersion, + bool includeAllPlatforms = true, + }) async { + final bool includeAllPlatforms = cache.includeAllPlatforms; cache.includeAllPlatforms = includeAllPlatforms; - - Future checkForArtifacts(String engineVersion) async { - engineVersion ??= version; - final String url = '$_storageBaseUrl/flutter_infra/flutter/$engineVersion/'; - - bool exists = false; - for (String pkgName in _getPackageDirs()) { - exists = await _doesRemoteExist('Checking package $pkgName is available...', - Uri.parse(url + pkgName + '.zip')); + engineVersion ??= version; + final String url = '$_storageBaseUrl/flutter_infra/flutter/$engineVersion/'; + final bool result = await () async { + for (BinaryArtifact packageArtifact in _packages) { + final Uri uri = Uri.parse('$url${packageArtifact.name}.zip'); + final bool exists = await _doesRemoteExist('Checking package ${packageArtifact.name} is available...', uri); if (!exists) { return false; } } - - for (List toolsDir in _getBinaryDirs()) { - final String cacheDir = toolsDir[0]; - final String urlPath = toolsDir[1]; - exists = await _doesRemoteExist('Checking $cacheDir tools are available...', - Uri.parse(url + urlPath)); + final List rawArtifacts = getBinaryDirs(buildModes: [], targetPlatforms: [], skipUnknown: false); + for (BinaryArtifact rawArtifact in rawArtifacts) { + final Uri uri = Uri.parse('$url${rawArtifact.fileName}'); + final bool exists = await _doesRemoteExist('Checking ${rawArtifact.name} tools are available...', uri); if (!exists) { return false; } } - return true; - } - - final bool result = await checkForArtifacts(engineVersion); - cache.includeAllPlatforms = includeAllPlatformsState; + }(); + cache.includeAllPlatforms = includeAllPlatforms; return result; } @@ -565,8 +957,9 @@ class FlutterEngine extends CachedArtifact { for (FileSystemEntity entity in dir.listSync()) { if (entity is File) { final String name = fs.path.basename(entity.path); - if (name == 'flutter_tester') + if (name == 'flutter_tester') { os.makeExecutable(entity); + } } } } @@ -581,30 +974,44 @@ class GradleWrapper extends CachedArtifact { String get _gradleWrapper => fs.path.join('gradle', 'wrapper', 'gradle-wrapper.jar'); @override - Future updateInner() { + Future updateInner({List buildModes, List targetPlatforms, bool skipUnknown, bool clobber}) async { final Uri archiveUri = _toStorageUri(version); - return _downloadZippedTarball('Downloading Gradle Wrapper...', archiveUri, location).then((_) { - // Delete property file, allowing templates to provide it. - fs.file(fs.path.join(location.path, 'gradle', 'wrapper', 'gradle-wrapper.properties')).deleteSync(); - // Remove NOTICE file. Should not be part of the template. - fs.file(fs.path.join(location.path, 'NOTICE')).deleteSync(); - }); + if (fs.directory(location).listSync().isEmpty || clobber) { + await _downloadZippedTarball('Downloading Gradle Wrapper...', archiveUri, location).then((_) { + // Delete property file, allowing templates to provide it. + fs.file(fs.path.join(location.path, 'gradle', 'wrapper', 'gradle-wrapper.properties')).deleteSync(); + // Remove NOTICE file. Should not be part of the template. + fs.file(fs.path.join(location.path, 'NOTICE')).deleteSync(); + }); + } } @override - bool isUpToDateInner() { + UpdateResult isUpToDate({ + List buildModes = const [], + List targetPlatforms = const [], + bool skipUnknown = true, + }) { + final UpdateResult parentResult = super.isUpToDate( + buildModes: buildModes, + targetPlatforms: targetPlatforms, + skipUnknown: skipUnknown, + ); + if (!parentResult.isUpToDate || parentResult.clobber) { + return parentResult; + } final Directory wrapperDir = cache.getCacheDir(fs.path.join('artifacts', 'gradle_wrapper')); if (!fs.directory(wrapperDir).existsSync()) - return false; + return const UpdateResult(isUpToDate: false); for (String scriptName in _gradleScripts) { final File scriptFile = fs.file(fs.path.join(wrapperDir.path, scriptName)); if (!scriptFile.existsSync()) - return false; + return const UpdateResult(isUpToDate: false); } final File gradleWrapperJar = fs.file(fs.path.join(wrapperDir.path, _gradleWrapper)); if (!gradleWrapperJar.existsSync()) - return false; - return true; + return const UpdateResult(isUpToDate: false); + return const UpdateResult(isUpToDate: true); } } @@ -659,3 +1066,34 @@ void _ensureExists(Directory directory) { if (!directory.existsSync()) directory.createSync(recursive: true); } + +class BinaryArtifact { + const BinaryArtifact({ + this.targetPlatform, + this.buildMode, + @required this.name, + @required this.fileName, + this.hostPlatform, + this.skipChecks = false, + }); + + final TargetPlatform targetPlatform; + final TargetPlatform hostPlatform; + final BuildMode buildMode; + final String name; + final String fileName; + final bool skipChecks; + + /// The location where this artifact will be cached. + Directory artifactLocation(Directory location) { + return fs.directory(fs.path.join(location.path, name)); + } + + /// The remote location where this artifact can be downloaded. + Uri artifactRemoteLocation(String baseUrl) { + return Uri.parse('$baseUrl$fileName'); + } + + @override + String toString() => '$name/$fileName'; +} diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart index c31f741718..210056256e 100644 --- a/packages/flutter_tools/lib/src/commands/build.dart +++ b/packages/flutter_tools/lib/src/commands/build.dart @@ -4,6 +4,8 @@ import 'dart:async'; +import '../build_info.dart'; +import '../globals.dart'; import '../runner/flutter_command.dart'; import 'build_aot.dart'; import 'build_apk.dart'; @@ -36,4 +38,19 @@ abstract class BuildSubCommand extends FlutterCommand { BuildSubCommand() { requiresPubspecYaml(); } + + @override + Future updateCache() async { + final BuildInfo buildInfo = getBuildInfo(); + bool skipUnknown = false; + if (buildInfo.mode == null || buildInfo.targetPlatform == null) { + skipUnknown = true; + } + await cache.updateAll( + buildModes: buildInfo.mode != null ? [buildInfo.mode] : [], + targetPlatforms: buildInfo.targetPlatform != null ? [buildInfo.targetPlatform] : [], + clobber: false, + skipUnknown: skipUnknown, + ); + } } diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index 2660932b2d..d5a35f3992 100644 --- a/packages/flutter_tools/lib/src/commands/build_apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart @@ -5,6 +5,8 @@ import 'dart:async'; import '../android/apk.dart'; +import '../build_info.dart'; +import '../globals.dart'; import '../project.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; @@ -49,4 +51,23 @@ class BuildApkCommand extends BuildSubCommand { ); return null; } + + @override + Future updateCache() async { + final BuildInfo buildInfo = getBuildInfo(); + await cache.updateAll( + buildModes: [ + buildInfo.mode, + BuildMode.debug, + ], + targetPlatforms: [ + TargetPlatform.android_arm, + TargetPlatform.android_arm64, + TargetPlatform.android_x64, + TargetPlatform.android_x86 + ], + clobber: false, + skipUnknown: true, + ); + } } diff --git a/packages/flutter_tools/lib/src/commands/build_appbundle.dart b/packages/flutter_tools/lib/src/commands/build_appbundle.dart index bf8979a7c1..bbe1b1bcff 100644 --- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart @@ -5,6 +5,8 @@ import 'dart:async'; import '../android/app_bundle.dart'; +import '../build_info.dart'; +import '../globals.dart'; import '../project.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; @@ -47,4 +49,20 @@ class BuildAppBundleCommand extends BuildSubCommand { ); return null; } + + @override + Future updateCache() async { + final BuildInfo buildInfo = getBuildInfo(); + await cache.updateAll( + buildModes: [ + buildInfo.mode, + ], + targetPlatforms: [ + TargetPlatform.android_arm, + TargetPlatform.android_arm64, + ], + clobber: false, + skipUnknown: true, + ); + } } diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index b90e332975..8205c72f55 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -49,9 +49,15 @@ class BuildIOSCommand extends BuildSubCommand { final String description = 'Build an iOS application bundle (Mac OS X host only).'; @override - Future runCommand() async { - final bool forSimulator = argResults['simulator']; + Future validateCommand() async { defaultBuildMode = forSimulator ? BuildMode.debug : BuildMode.release; + return super.validateCommand(); + } + + bool get forSimulator => argResults['simulator']; + + @override + Future runCommand() async { if (getCurrentHostPlatform() != HostPlatform.darwin_x64) throwToolExit('Building for iOS is only supported on the Mac.'); diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 2a13c4c398..e8b7cf437e 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -211,7 +211,10 @@ class CreateCommand extends FlutterCommand { throwToolExit('Neither the --flutter-root command line flag nor the FLUTTER_ROOT environment ' 'variable was specified. Unable to find package:flutter.', exitCode: 2); - await Cache.instance.updateAll(); + await Cache.instance.updateAll( + skipUnknown: true, + clobber: false, + ); final String flutterRoot = fs.path.absolute(Cache.flutterRoot); diff --git a/packages/flutter_tools/lib/src/commands/doctor.dart b/packages/flutter_tools/lib/src/commands/doctor.dart index 84480a8174..48dc736bcc 100644 --- a/packages/flutter_tools/lib/src/commands/doctor.dart +++ b/packages/flutter_tools/lib/src/commands/doctor.dart @@ -6,6 +6,7 @@ import 'dart:async'; import '../base/common.dart'; import '../doctor.dart'; +import '../globals.dart'; import '../runner/flutter_command.dart'; class DoctorCommand extends FlutterCommand { @@ -48,4 +49,12 @@ class DoctorCommand extends FlutterCommand { final bool success = await doctor.diagnose(androidLicenses: argResults['android-licenses'], verbose: verbose); return FlutterCommandResult(success ? ExitStatus.success : ExitStatus.warning); } + + @override + Future updateCache() async { + await cache.updateAll( + clobber: false, + skipUnknown: true, + ); + } } diff --git a/packages/flutter_tools/lib/src/commands/ide_config.dart b/packages/flutter_tools/lib/src/commands/ide_config.dart index f153b3be56..e9021de46a 100644 --- a/packages/flutter_tools/lib/src/commands/ide_config.dart +++ b/packages/flutter_tools/lib/src/commands/ide_config.dart @@ -223,8 +223,6 @@ class IdeConfigCommand extends FlutterCommand { throwToolExit('Currently, the only supported IDE is IntelliJ\n$usage', exitCode: 2); } - await Cache.instance.updateAll(); - if (argResults['update-templates']) { _handleTemplateUpdate(); return null; diff --git a/packages/flutter_tools/lib/src/commands/precache.dart b/packages/flutter_tools/lib/src/commands/precache.dart index b1fc4dcc77..7137937ecb 100644 --- a/packages/flutter_tools/lib/src/commands/precache.dart +++ b/packages/flutter_tools/lib/src/commands/precache.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import '../cache.dart'; import '../globals.dart'; import '../runner/flutter_command.dart'; @@ -11,6 +12,8 @@ class PrecacheCommand extends FlutterCommand { PrecacheCommand() { argParser.addFlag('all-platforms', abbr: 'a', negatable: false, help: 'Precache artifacts for all platforms.'); + argParser.addFlag('force', abbr: 'f', negatable: false, + help: 'Force download of new cached artifacts'); } @override @@ -24,14 +27,18 @@ class PrecacheCommand extends FlutterCommand { @override Future runCommand() async { - if (argResults['all-platforms']) + if (argResults['all-platforms']) { cache.includeAllPlatforms = true; - - if (cache.isUpToDate()) + } + final UpdateResult result = cache.isUpToDate(skipUnknown: false); + if (result.isUpToDate && !result.clobber && !argResults['force']) { printStatus('Already up-to-date.'); - else - await cache.updateAll(); - - return null; + } else { + await cache.updateAll( + skipUnknown: false, + clobber: argResults['force'] || result.clobber, + ); + } + return const FlutterCommandResult(ExitStatus.success); } } diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 1a31645b09..6624093519 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -61,6 +61,34 @@ abstract class RunCommandBase extends FlutterCommand { bool get traceStartup => argResults['trace-startup']; String get route => argResults['route']; + + @override + bool get shouldUpdateCache => true; + + @override + Future updateCache() async { + final BuildInfo buildInfo = getBuildInfo(); + final BuildMode buildMode = buildInfo.mode ?? BuildMode.debug; + final Set targetPlatforms = Set(); + if (buildInfo.targetPlatform != null) { + targetPlatforms.add(buildInfo.targetPlatform); + } + await for (Device device in deviceManager.getAllConnectedDevices()) { + targetPlatforms.add(await device.targetPlatform); + } + if (targetPlatforms.contains(TargetPlatform.android_arm) || targetPlatforms.contains(TargetPlatform.android_arm64)) { + targetPlatforms.add(TargetPlatform.android_x64); + targetPlatforms.add(TargetPlatform.android_x86); + targetPlatforms.add(TargetPlatform.android_arm); + targetPlatforms.add(TargetPlatform.android_arm64); + } + await cache.updateAll( + buildModes: [buildMode], + targetPlatforms: targetPlatforms.toList(), + clobber: false, + skipUnknown: false, + ); + } } class RunCommand extends RunCommandBase { diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index f3c903dcb0..dc1b0a3b0a 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -323,30 +323,43 @@ abstract class FlutterCommand extends Command { } BuildMode getBuildMode() { - final List modeFlags = [argResults['debug'], argResults['profile'], argResults['release']]; - if (modeFlags.where((bool flag) => flag).length > 1) + bool debug; + bool profile; + bool release; + if (argParser.options.containsKey('debug')) { + debug = argResults['debug']; + } else { + debug = _defaultBuildMode == BuildMode.debug; + } + if (argParser.options.containsKey('profile')) { + profile = argResults['profile']; + } else { + profile = _defaultBuildMode == BuildMode.profile; + } + if (argParser.options.containsKey('release')) { + release = argResults['release']; + } else { + release = _defaultBuildMode == BuildMode.release; + } + if (debug && profile || debug && release || release && profile) { throw UsageException('Only one of --debug, --profile, or --release can be specified.', null); + } final bool dynamicFlag = argParser.options.containsKey('dynamic') ? argResults['dynamic'] : false; - if (argResults['debug']) { - if (dynamicFlag) + if (debug) { + if (dynamicFlag) { throw ToolExit('Error: --dynamic requires --release or --profile.'); + } return BuildMode.debug; } - if (argResults['profile']) + if (profile) { return dynamicFlag ? BuildMode.dynamicProfile : BuildMode.profile; - if (argResults['release']) + } + if (release) { return dynamicFlag ? BuildMode.dynamicRelease : BuildMode.release; - - if (_defaultBuildMode == BuildMode.debug && dynamicFlag) - throw ToolExit('Error: --dynamic requires --release or --profile.'); - if (_defaultBuildMode == BuildMode.release && dynamicFlag) - return BuildMode.dynamicRelease; - if (_defaultBuildMode == BuildMode.profile && dynamicFlag) - return BuildMode.dynamicProfile; - + } return _defaultBuildMode; } @@ -384,7 +397,7 @@ abstract class FlutterCommand extends Command { '--patch-number (${argResults['patch-number']}) must be an int.', null); } - String extraFrontEndOptions = + List extraFrontEndOptions = argParser.options.containsKey(FlutterOptions.kExtraFrontEndOptions) ? argResults[FlutterOptions.kExtraFrontEndOptions] : null; @@ -393,9 +406,9 @@ abstract class FlutterCommand extends Command { for (String expFlag in argResults[FlutterOptions.kEnableExperiment]) { final String flag = '--enable-experiment=' + expFlag; if (extraFrontEndOptions != null) { - extraFrontEndOptions += ',' + flag; + extraFrontEndOptions.add(flag); } else { - extraFrontEndOptions = flag; + extraFrontEndOptions = [flag]; } } } @@ -421,9 +434,9 @@ abstract class FlutterCommand extends Command { baselineDir: argParser.options.containsKey('baseline-dir') ? argResults['baseline-dir'] : null, - extraFrontEndOptions: extraFrontEndOptions, + extraFrontEndOptions: extraFrontEndOptions?.join(', '), extraGenSnapshotOptions: argParser.options.containsKey(FlutterOptions.kExtraGenSnapshotOptions) - ? argResults[FlutterOptions.kExtraGenSnapshotOptions] + ? argResults[FlutterOptions.kExtraGenSnapshotOptions]?.join(', ') : null, buildSharedLibrary: argParser.options.containsKey('build-shared-library') ? argResults['build-shared-library'] @@ -513,6 +526,19 @@ abstract class FlutterCommand extends Command { ); } + /// A hook called to populate the cache with a particular target platform + /// or build mode. + /// + /// If a command requires specific artifacts, it is it's responsibility to + /// request them here. + Future updateCache() async { + // Only download the minimum set of binaries. + await cache.updateAll( + clobber: false, + skipUnknown: true, + ); + } + /// Perform validation then call [runCommand] to execute the command. /// Return a [Future] that completes with an exit code /// indicating whether execution was successful. @@ -523,11 +549,11 @@ abstract class FlutterCommand extends Command { @mustCallSuper Future verifyThenRunCommand(String commandPath) async { await validateCommand(); - // Populate the cache. We call this before pub get below so that the sky_engine // package is available in the flutter cache for pub to find. - if (shouldUpdateCache) - await cache.updateAll(); + if (shouldUpdateCache) { + await updateCache(); + } if (shouldRunPub) { await pubGet(context: PubContext.getVerifyContext(name)); diff --git a/packages/flutter_tools/test/cache_test.dart b/packages/flutter_tools/test/cache_test.dart index 03912cb446..008e0cef30 100644 --- a/packages/flutter_tools/test/cache_test.dart +++ b/packages/flutter_tools/test/cache_test.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:file/file.dart'; import 'package:file/memory.dart'; +import 'package:flutter_tools/src/build_info.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; @@ -60,7 +61,8 @@ void main() { directory.createSync(recursive: true); fs.file(fs.path.join(directory.path, 'artifacts', 'gradle_wrapper', 'gradle', 'wrapper', 'gradle-wrapper.jar')).createSync(recursive: true); when(mockCache.getCacheDir(fs.path.join('artifacts', 'gradle_wrapper'))).thenReturn(fs.directory(fs.path.join(directory.path, 'artifacts', 'gradle_wrapper'))); - expect(gradleWrapper.isUpToDateInner(), false); + when(mockCache.getArtifactDirectory('gradle_wrapper')).thenReturn(fs.directory(fs.path.join(directory.path, 'artifacts', 'gradle_wrapper'))); + expect(gradleWrapper.isUpToDate().isUpToDate, const UpdateResult(isUpToDate: false).isUpToDate); }, overrides: { Cache: ()=> mockCache, FileSystem: () => fs @@ -75,7 +77,8 @@ void main() { fs.file(fs.path.join(directory.path, 'artifacts', 'gradle_wrapper', 'gradlew.bat')).createSync(recursive: true); when(mockCache.getCacheDir(fs.path.join('artifacts', 'gradle_wrapper'))).thenReturn(fs.directory(fs.path.join(directory.path, 'artifacts', 'gradle_wrapper'))); - expect(gradleWrapper.isUpToDateInner(), true); + when(mockCache.getArtifactDirectory('gradle_wrapper')).thenReturn(fs.directory(fs.path.join(directory.path, 'artifacts', 'gradle_wrapper'))); + expect(gradleWrapper.isUpToDate().isUpToDate, const UpdateResult(isUpToDate: true).isUpToDate); }, overrides: { Cache: ()=> mockCache, FileSystem: () => fs @@ -84,37 +87,37 @@ void main() { test('should not be up to date, if some cached artifact is not', () { final CachedArtifact artifact1 = MockCachedArtifact(); final CachedArtifact artifact2 = MockCachedArtifact(); - when(artifact1.isUpToDate()).thenReturn(true); - when(artifact2.isUpToDate()).thenReturn(false); + when(artifact1.isUpToDate(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'))).thenReturn(const UpdateResult(isUpToDate: true, clobber: false)); + when(artifact2.isUpToDate(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'))).thenReturn(const UpdateResult(isUpToDate: false, clobber: false)); final Cache cache = Cache(artifacts: [artifact1, artifact2]); - expect(cache.isUpToDate(), isFalse); + expect(cache.isUpToDate().isUpToDate, isFalse); }); test('should be up to date, if all cached artifacts are', () { final CachedArtifact artifact1 = MockCachedArtifact(); final CachedArtifact artifact2 = MockCachedArtifact(); - when(artifact1.isUpToDate()).thenReturn(true); - when(artifact2.isUpToDate()).thenReturn(true); + when(artifact1.isUpToDate(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'))).thenReturn(const UpdateResult(isUpToDate: true, clobber: false)); + when(artifact2.isUpToDate(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'))).thenReturn(const UpdateResult(isUpToDate: true, clobber: false)); final Cache cache = Cache(artifacts: [artifact1, artifact2]); - expect(cache.isUpToDate(), isTrue); + expect(cache.isUpToDate().isUpToDate, isTrue); }); test('should update cached artifacts which are not up to date', () async { final CachedArtifact artifact1 = MockCachedArtifact(); final CachedArtifact artifact2 = MockCachedArtifact(); - when(artifact1.isUpToDate()).thenReturn(true); - when(artifact2.isUpToDate()).thenReturn(false); + when(artifact1.isUpToDate(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'))).thenReturn(const UpdateResult(isUpToDate: true, clobber: false)); + when(artifact2.isUpToDate(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'))).thenReturn(const UpdateResult(isUpToDate: false, clobber: false)); final Cache cache = Cache(artifacts: [artifact1, artifact2]); await cache.updateAll(); - verifyNever(artifact1.update()); - verify(artifact2.update()); + verifyNever(artifact1.update(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'))); + verify(artifact2.update(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'), clobber: anyNamed('clobber'))); }); testUsingContext('failed storage.googleapis.com download shows China warning', () async { final CachedArtifact artifact1 = MockCachedArtifact(); final CachedArtifact artifact2 = MockCachedArtifact(); - when(artifact1.isUpToDate()).thenReturn(false); - when(artifact2.isUpToDate()).thenReturn(false); + when(artifact1.isUpToDate(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'))).thenReturn(const UpdateResult(isUpToDate: false, clobber: false)); + when(artifact2.isUpToDate(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'))).thenReturn(const UpdateResult(isUpToDate: false, clobber: false)); final MockInternetAddress address = MockInternetAddress(); when(address.host).thenReturn('storage.googleapis.com'); - when(artifact1.update()).thenThrow(SocketException( + when(artifact1.update(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'), clobber: anyNamed('clobber'))).thenThrow(SocketException( 'Connection reset by peer', address: address, )); @@ -123,15 +126,203 @@ void main() { await cache.updateAll(); fail('Mock thrown exception expected'); } catch (e) { - verify(artifact1.update()); + verify(artifact1.update(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'), clobber: anyNamed('clobber'))); // Don't continue when retrieval fails. - verifyNever(artifact2.update()); + verifyNever(artifact2.update(buildModes: anyNamed('buildModes'), targetPlatforms: anyNamed('targetPlatforms'), skipUnknown: anyNamed('skipUnknown'), clobber: anyNamed('clobber'))); expect( testLogger.errorText, contains('https://flutter.io/community/china'), ); } }); + + final MockPlatform macos = MockPlatform(); + final MockPlatform windows = MockPlatform(); + final MockPlatform linux = MockPlatform(); + when(macos.isMacOS).thenReturn(true); + when(macos.isLinux).thenReturn(false); + when(macos.isWindows).thenReturn(false); + when(windows.isMacOS).thenReturn(false); + when(windows.isLinux).thenReturn(false); + when(windows.isWindows).thenReturn(true); + when(linux.isMacOS).thenReturn(false); + when(linux.isLinux).thenReturn(true); + when(linux.isWindows).thenReturn(false); + + testUsingContext('Engine cache filtering - macOS', () { + final FlutterEngine flutterEngine = FlutterEngine(MockCache()); + expect(flutterEngine.getBinaryDirs( + buildModes: [BuildMode.release], + targetPlatforms: [TargetPlatform.android_arm], + skipUnknown: true, + ), unorderedEquals(const [ + BinaryArtifact( + name: 'common', + fileName: 'flutter_patched_sdk.zip', + ), + BinaryArtifact( + name: 'android-arm-release', + fileName: 'android-arm-release/artifacts.zip', + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm-profile/darwin-x64', + fileName: 'android-arm-profile/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.profile, + targetPlatform: TargetPlatform.android_arm, + skipChecks: true, + ), + BinaryArtifact( + name: 'android-arm-release/darwin-x64', + fileName: 'android-arm-release/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'darwin-x64', + fileName: 'darwin-x64/artifacts.zip', + hostPlatform: TargetPlatform.darwin_x64, + ), + ])); + }, overrides: { + Platform: () => macos, + }); + + testUsingContext('Engine cache filtering - unknown mode - macOS', () { + final FlutterEngine flutterEngine = FlutterEngine(MockCache()); + expect(flutterEngine.getBinaryDirs( + buildModes: [], + targetPlatforms: [TargetPlatform.ios], + skipUnknown: true, + ), unorderedEquals(const [ + BinaryArtifact( + name: 'common', + fileName: 'flutter_patched_sdk.zip', + ), + BinaryArtifact( + name: 'android-arm-profile/darwin-x64', + fileName: 'android-arm-profile/darwin-x64.zip', + hostPlatform: TargetPlatform.darwin_x64, + buildMode: BuildMode.profile, + targetPlatform: TargetPlatform.android_arm, + skipChecks: true, + ), + BinaryArtifact( + name: 'ios', fileName: 'ios/artifacts.zip', + buildMode: BuildMode.debug, + hostPlatform: TargetPlatform.darwin_x64, + targetPlatform: TargetPlatform.ios, + ), + BinaryArtifact( + name: 'ios-profile', + fileName: 'ios-profile/artifacts.zip', + buildMode: BuildMode.profile, + hostPlatform: TargetPlatform.darwin_x64, + targetPlatform: TargetPlatform.ios, + ), + BinaryArtifact( + name: 'ios-release', + fileName: 'ios-release/artifacts.zip', + buildMode: BuildMode.release, + hostPlatform: TargetPlatform.darwin_x64, + targetPlatform: TargetPlatform.ios, + ), + BinaryArtifact( + name: 'darwin-x64', + fileName: 'darwin-x64/artifacts.zip', + hostPlatform: TargetPlatform.darwin_x64, + ), + ])); + }, overrides: { + Platform: () => macos, + }); + + testUsingContext('Engine cache filtering - Windows', () { + final FlutterEngine flutterEngine = FlutterEngine(MockCache()); + expect(flutterEngine.getBinaryDirs( + buildModes: [BuildMode.release], + targetPlatforms: [TargetPlatform.android_arm], + skipUnknown: true, + ), unorderedEquals(const [ + BinaryArtifact( + name: 'common', + fileName: 'flutter_patched_sdk.zip', + ), + BinaryArtifact( + name: 'android-arm-release', + fileName: 'android-arm-release/artifacts.zip', + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm-profile/windows-x64', + fileName: 'android-arm-profile/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + buildMode: BuildMode.profile, + targetPlatform: TargetPlatform.android_arm, + skipChecks: true, + ), + BinaryArtifact( + name: 'android-arm-release/windows-x64', + fileName: 'android-arm-release/windows-x64.zip', + hostPlatform: TargetPlatform.windows_x64, + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'windows-x64', + fileName: 'windows-x64/artifacts.zip', + hostPlatform: TargetPlatform.windows_x64, + ), + ])); + }, overrides: { + Platform: () => windows, + }); + + testUsingContext('Engine cache filtering - linux', () { + final FlutterEngine flutterEngine = FlutterEngine(MockCache()); + expect(flutterEngine.getBinaryDirs( + buildModes: [BuildMode.release], + targetPlatforms: [TargetPlatform.android_arm], + skipUnknown: true, + ), unorderedEquals(const [ + BinaryArtifact( + name: 'common', + fileName: 'flutter_patched_sdk.zip', + ), + BinaryArtifact( + name: 'android-arm-release', + fileName: 'android-arm-release/artifacts.zip', + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'android-arm-profile/linux-x64', + fileName: 'android-arm-profile/linux-x64.zip', + hostPlatform: TargetPlatform.linux_x64, + buildMode: BuildMode.profile, + targetPlatform: TargetPlatform.android_arm, + skipChecks: true, + ), + BinaryArtifact( + name: 'android-arm-release/linux-x64', + fileName: 'android-arm-release/linux-x64.zip', + hostPlatform: TargetPlatform.linux_x64, + buildMode: BuildMode.release, + targetPlatform: TargetPlatform.android_arm, + ), + BinaryArtifact( + name: 'linux-x64', + fileName: 'linux-x64/artifacts.zip', + hostPlatform: TargetPlatform.linux_x64, + ), + ])); + }, overrides: { + Platform: () => linux, + }); }); testUsingContext('flattenNameSubdirs', () { @@ -162,4 +353,5 @@ class MockFile extends Mock implements File { class MockRandomAccessFile extends Mock implements RandomAccessFile {} class MockCachedArtifact extends Mock implements CachedArtifact {} class MockInternetAddress extends Mock implements InternetAddress {} -class MockCache extends Mock implements Cache {} \ No newline at end of file +class MockCache extends Mock implements Cache {} +class MockPlatform extends Mock implements Platform {} diff --git a/packages/flutter_tools/test/runner/flutter_command_test.dart b/packages/flutter_tools/test/runner/flutter_command_test.dart index 3b144478e4..7aee843db9 100644 --- a/packages/flutter_tools/test/runner/flutter_command_test.dart +++ b/packages/flutter_tools/test/runner/flutter_command_test.dart @@ -42,7 +42,12 @@ void main() { testUsingContext('honors shouldUpdateCache true', () async { final DummyFlutterCommand flutterCommand = DummyFlutterCommand(shouldUpdateCache: true); await flutterCommand.run(); - verify(cache.updateAll()).called(1); + verify(cache.updateAll( + buildModes: anyNamed('buildModes'), + clobber: anyNamed('clobber'), + skipUnknown: anyNamed('skipUnknown'), + targetPlatforms: anyNamed('targetPlatforms') + )).called(1); }, overrides: { Cache: () => cache,