Unverified Commit 07e2d6f6 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] make precache force blow away stamp files (#61003)

update flutter precache --force to delete all stamp files. In the event that a user is hitting a cache issue, this should be easier than re-downloading all artifacts or manually blowing away the cache.

This is probably how it should have worked in the first place
parent 77310c15
...@@ -48,6 +48,7 @@ import 'src/commands/train.dart'; ...@@ -48,6 +48,7 @@ import 'src/commands/train.dart';
import 'src/commands/update_packages.dart'; import 'src/commands/update_packages.dart';
import 'src/commands/upgrade.dart'; import 'src/commands/upgrade.dart';
import 'src/commands/version.dart'; import 'src/commands/version.dart';
import 'src/features.dart';
import 'src/globals.dart' as globals; import 'src/globals.dart' as globals;
import 'src/runner/flutter_command.dart'; import 'src/runner/flutter_command.dart';
import 'src/web/compile.dart'; import 'src/web/compile.dart';
...@@ -98,7 +99,13 @@ Future<void> main(List<String> args) async { ...@@ -98,7 +99,13 @@ Future<void> main(List<String> args) async {
LogsCommand(), LogsCommand(),
MakeHostAppEditableCommand(), MakeHostAppEditableCommand(),
PackagesCommand(), PackagesCommand(),
PrecacheCommand(verboseHelp: verboseHelp), PrecacheCommand(
verboseHelp: verboseHelp,
cache: globals.cache,
logger: globals.logger,
platform: globals.platform,
featureFlags: featureFlags,
),
RunCommand(verboseHelp: verboseHelp), RunCommand(verboseHelp: verboseHelp),
ScreenshotCommand(), ScreenshotCommand(),
ShellCompletionCommand(), ShellCompletionCommand(),
......
...@@ -37,6 +37,7 @@ class DevelopmentArtifact { ...@@ -37,6 +37,7 @@ class DevelopmentArtifact {
/// Artifacts required for Android development. /// Artifacts required for Android development.
static const DevelopmentArtifact androidGenSnapshot = DevelopmentArtifact._('android_gen_snapshot'); static const DevelopmentArtifact androidGenSnapshot = DevelopmentArtifact._('android_gen_snapshot');
static const DevelopmentArtifact androidMaven = DevelopmentArtifact._('android_maven'); static const DevelopmentArtifact androidMaven = DevelopmentArtifact._('android_maven');
// Artifacts used for internal builds. // Artifacts used for internal builds.
static const DevelopmentArtifact androidInternalBuild = DevelopmentArtifact._('android_internal_build'); static const DevelopmentArtifact androidInternalBuild = DevelopmentArtifact._('android_internal_build');
...@@ -243,8 +244,8 @@ class Cache { ...@@ -243,8 +244,8 @@ class Cache {
/// Checks if the current process owns the lock for the cache directory at /// Checks if the current process owns the lock for the cache directory at
/// this very moment; throws a [StateError] if it doesn't. /// this very moment; throws a [StateError] if it doesn't.
static void checkLockAcquired() { static void checkLockAcquired([Platform platform]) {
if (_lockEnabled && _lock == null && globals.platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') { if (_lockEnabled && _lock == null && (platform ?? globals.platform).environment['FLUTTER_ALREADY_LOCKED'] != 'true') {
throw StateError( throw StateError(
'The current process does not own the lock for the cache directory. This is a bug in Flutter CLI tools.', 'The current process does not own the lock for the cache directory. This is a bug in Flutter CLI tools.',
); );
...@@ -372,6 +373,21 @@ class Cache { ...@@ -372,6 +373,21 @@ class Cache {
return versionFile.existsSync() ? versionFile.readAsStringSync().trim() : null; return versionFile.existsSync() ? versionFile.readAsStringSync().trim() : null;
} }
/// Delete all stamp files maintained by the cache.
void clearStampFiles() {
try {
getStampFileFor('flutter_tools').deleteSync();
for (final ArtifactSet artifact in _artifacts) {
final File file = getStampFileFor(artifact.stampName);
if (file.existsSync()) {
file.deleteSync();
}
}
} on FileSystemException catch (err) {
_logger.printError('Failed to delete some stamp files: $err');
}
}
String getStampFor(String artifactName) { String getStampFor(String artifactName) {
final File stampFile = getStampFileFor(artifactName); final File stampFile = getStampFileFor(artifactName);
return stampFile.existsSync() ? stampFile.readAsStringSync().trim() : null; return stampFile.existsSync() ? stampFile.readAsStringSync().trim() : null;
...@@ -508,6 +524,13 @@ abstract class ArtifactSet { ...@@ -508,6 +524,13 @@ abstract class ArtifactSet {
/// Updates the artifact. /// Updates the artifact.
Future<void> update(); Future<void> update();
/// The canonical name of the artifact.
String get name;
// The name of the stamp file. Defaults to the same as the
// artifact name.
String get stampName => name;
} }
/// An artifact set managed by the cache. /// An artifact set managed by the cache.
...@@ -520,11 +543,10 @@ abstract class CachedArtifact extends ArtifactSet { ...@@ -520,11 +543,10 @@ abstract class CachedArtifact extends ArtifactSet {
final Cache cache; final Cache cache;
/// The canonical name of the artifact. @override
final String name; final String name;
// The name of the stamp file. Defaults to the same as the @override
// artifact name.
String get stampName => name; String get stampName => name;
Directory get location => cache.getArtifactDirectory(name); Directory get location => cache.getArtifactDirectory(name);
...@@ -1043,6 +1065,9 @@ class AndroidMavenArtifacts extends ArtifactSet { ...@@ -1043,6 +1065,9 @@ class AndroidMavenArtifacts extends ArtifactSet {
// Therefore, call Gradle to figure this out. // Therefore, call Gradle to figure this out.
return false; return false;
} }
@override
String get name => 'android-maven-artifacts';
} }
class IOSEngineArtifacts extends EngineCachedArtifact { class IOSEngineArtifacts extends EngineCachedArtifact {
......
...@@ -4,18 +4,36 @@ ...@@ -4,18 +4,36 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../cache.dart'; import '../cache.dart';
import '../features.dart'; import '../features.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../version.dart';
/// The flutter precache command allows downloading of cache artifacts without
/// the use of device/artifact autodetection.
class PrecacheCommand extends FlutterCommand { class PrecacheCommand extends FlutterCommand {
PrecacheCommand({bool verboseHelp = false}) { PrecacheCommand({
bool verboseHelp = false,
@required Cache cache,
@required Platform platform,
@required Logger logger,
@required FeatureFlags featureFlags,
FlutterVersion flutterVersion, // flutter version cannot be injected.
}) : _cache = cache,
_platform = platform,
_logger = logger,
_featureFlags = featureFlags,
_flutterVersion = flutterVersion {
argParser.addFlag('all-platforms', abbr: 'a', negatable: false, argParser.addFlag('all-platforms', abbr: 'a', negatable: false,
help: 'Precache artifacts for all host platforms.'); help: 'Precache artifacts for all host platforms.');
argParser.addFlag('force', abbr: 'f', negatable: false, argParser.addFlag('force', abbr: 'f', negatable: false,
help: 'Force downloading of artifacts.'); help: 'Force re-downloading of artifacts.');
argParser.addFlag('android', negatable: true, defaultsTo: true, argParser.addFlag('android', negatable: true, defaultsTo: true,
help: 'Precache artifacts for Android development.', help: 'Precache artifacts for Android development.',
hide: verboseHelp); hide: verboseHelp);
...@@ -48,6 +66,12 @@ class PrecacheCommand extends FlutterCommand { ...@@ -48,6 +66,12 @@ class PrecacheCommand extends FlutterCommand {
help: 'Precache the unsigned mac binaries when available.', hide: true); help: 'Precache the unsigned mac binaries when available.', hide: true);
} }
final Cache _cache;
final Logger _logger;
final Platform _platform;
final FeatureFlags _featureFlags;
final FlutterVersion _flutterVersion;
@override @override
final String name = 'precache'; final String name = 'precache';
...@@ -112,26 +136,29 @@ class PrecacheCommand extends FlutterCommand { ...@@ -112,26 +136,29 @@ class PrecacheCommand extends FlutterCommand {
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
// Re-lock the cache. // Re-lock the cache.
if (globals.platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') { if (_platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') {
await Cache.lock(); await Cache.lock();
} }
if (boolArg('force')) {
_cache.clearStampFiles();
}
final bool includeAllPlatforms = boolArg('all-platforms'); final bool includeAllPlatforms = boolArg('all-platforms');
if (includeAllPlatforms) { if (includeAllPlatforms) {
globals.cache.includeAllPlatforms = true; _cache.includeAllPlatforms = true;
} }
if (boolArg('use-unsigned-mac-binaries')) { if (boolArg('use-unsigned-mac-binaries')) {
globals.cache.useUnsignedMacBinaries = true; _cache.useUnsignedMacBinaries = true;
} }
globals.cache.platformOverrideArtifacts = _explicitArtifactSelections(); _cache.platformOverrideArtifacts = _explicitArtifactSelections();
final Map<String, String> umbrellaForArtifact = _umbrellaForArtifactMap(); final Map<String, String> umbrellaForArtifact = _umbrellaForArtifactMap();
final Set<DevelopmentArtifact> requiredArtifacts = <DevelopmentArtifact>{}; final Set<DevelopmentArtifact> requiredArtifacts = <DevelopmentArtifact>{};
for (final DevelopmentArtifact artifact in DevelopmentArtifact.values) { for (final DevelopmentArtifact artifact in DevelopmentArtifact.values) {
// Don't include unstable artifacts on stable branches. // Don't include unstable artifacts on stable branches.
if (!globals.flutterVersion.isMaster && artifact.unstable) { if (!(_flutterVersion ?? globals.flutterVersion).isMaster && artifact.unstable) {
continue; continue;
} }
if (artifact.feature != null && !featureFlags.isEnabled(artifact.feature)) { if (artifact.feature != null && !_featureFlags.isEnabled(artifact.feature)) {
continue; continue;
} }
...@@ -140,11 +167,10 @@ class PrecacheCommand extends FlutterCommand { ...@@ -140,11 +167,10 @@ class PrecacheCommand extends FlutterCommand {
requiredArtifacts.add(artifact); requiredArtifacts.add(artifact);
} }
} }
final bool forceUpdate = boolArg('force'); if (!_cache.isUpToDate()) {
if (forceUpdate || !globals.cache.isUpToDate()) { await _cache.updateAll(requiredArtifacts);
await globals.cache.updateAll(requiredArtifacts);
} else { } else {
globals.printStatus('Already up-to-date.'); _logger.printStatus('Already up-to-date.');
} }
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
......
...@@ -9,6 +9,7 @@ import 'package:flutter_tools/src/android/gradle_utils.dart'; ...@@ -9,6 +9,7 @@ import 'package:flutter_tools/src/android/gradle_utils.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart' show InternetAddress, SocketException; import 'package:flutter_tools/src/base/io.dart' show InternetAddress, SocketException;
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/net.dart'; import 'package:flutter_tools/src/base/net.dart';
import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
...@@ -241,13 +242,13 @@ void main() { ...@@ -241,13 +242,13 @@ void main() {
}); });
testUsingContext('Invalid URI for FLUTTER_STORAGE_BASE_URL throws ToolExit', () async { testUsingContext('Invalid URI for FLUTTER_STORAGE_BASE_URL throws ToolExit', () async {
when(globals.platform.environment).thenReturn(const <String, String>{
'FLUTTER_STORAGE_BASE_URL': ' http://foo',
});
final Cache cache = Cache(); final Cache cache = Cache();
expect(() => cache.storageBaseUrl, throwsToolExit()); expect(() => cache.storageBaseUrl, throwsToolExit());
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Platform: () => MockPlatform(), Platform: () => FakePlatform(environment: <String, String>{
'FLUTTER_STORAGE_BASE_URL': ' http://foo',
}),
}); });
}); });
...@@ -624,6 +625,83 @@ void main() { ...@@ -624,6 +625,83 @@ void main() {
contains(contains('release')), contains(contains('release')),
])); ]));
}); });
testWithoutContext('Cache can delete stampfiles of artifacts', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final ArtifactSet artifactSet = MockIosUsbArtifacts();
final BufferLogger logger = BufferLogger.test();
when(artifactSet.stampName).thenReturn('STAMP');
final Cache cache = Cache(
artifacts: <ArtifactSet>[
artifactSet,
],
logger: logger,
fileSystem: fileSystem,
platform: FakePlatform(),
osUtils: MockOperatingSystemUtils(),
rootOverride: fileSystem.currentDirectory,
);
final File toolStampFile = fileSystem.file('bin/cache/flutter_tools.stamp');
final File stampFile = cache.getStampFileFor(artifactSet.stampName);
stampFile.createSync(recursive: true);
toolStampFile.createSync(recursive: true);
cache.clearStampFiles();
expect(logger.errorText, isEmpty);
expect(stampFile, isNot(exists));
expect(toolStampFile, isNot(exists));
});
testWithoutContext('Cache does not attempt to delete already missing stamp files', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final ArtifactSet artifactSet = MockIosUsbArtifacts();
final BufferLogger logger = BufferLogger.test();
when(artifactSet.stampName).thenReturn('STAMP');
final Cache cache = Cache(
artifacts: <ArtifactSet>[
artifactSet,
],
logger: logger,
fileSystem: fileSystem,
platform: FakePlatform(),
osUtils: MockOperatingSystemUtils(),
rootOverride: fileSystem.currentDirectory,
);
final File toolStampFile = fileSystem.file('bin/cache/flutter_tools.stamp');
final File stampFile = cache.getStampFileFor(artifactSet.stampName);
toolStampFile.createSync(recursive: true);
cache.clearStampFiles();
expect(logger.errorText, isEmpty);
expect(stampFile, isNot(exists));
expect(toolStampFile, isNot(exists));
});
testWithoutContext('Cache catches file system exception from missing tool stamp file', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final ArtifactSet artifactSet = MockIosUsbArtifacts();
final BufferLogger logger = BufferLogger.test();
when(artifactSet.stampName).thenReturn('STAMP');
final Cache cache = Cache(
artifacts: <ArtifactSet>[
artifactSet,
],
logger: logger,
fileSystem: fileSystem,
platform: FakePlatform(),
osUtils: MockOperatingSystemUtils(),
rootOverride: fileSystem.currentDirectory,
);
cache.clearStampFiles();
expect(logger.errorText, contains('Failed to delete some stamp files'));
});
} }
class FakeCachedArtifact extends EngineCachedArtifact { class FakeCachedArtifact extends EngineCachedArtifact {
...@@ -689,7 +767,6 @@ class MockIosUsbArtifacts extends Mock implements IosUsbArtifacts {} ...@@ -689,7 +767,6 @@ class MockIosUsbArtifacts extends Mock implements IosUsbArtifacts {}
class MockInternetAddress extends Mock implements InternetAddress {} class MockInternetAddress extends Mock implements InternetAddress {}
class MockCache extends Mock implements Cache {} class MockCache extends Mock implements Cache {}
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
class MockPlatform extends Mock implements Platform {}
class MockVersionedPackageResolver extends Mock implements VersionedPackageResolver {} class MockVersionedPackageResolver extends Mock implements VersionedPackageResolver {}
class MockHttpClientRequest extends Mock implements HttpClientRequest {} class MockHttpClientRequest extends Mock implements HttpClientRequest {}
......
...@@ -934,4 +934,7 @@ class FakeCache implements Cache { ...@@ -934,4 +934,7 @@ class FakeCache implements Cache {
Future<bool> doesRemoteExist(String message, Uri url) async { Future<bool> doesRemoteExist(String message, Uri url) async {
return true; return true;
} }
@override
void clearStampFiles() {}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment