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';
import 'src/commands/update_packages.dart';
import 'src/commands/upgrade.dart';
import 'src/commands/version.dart';
import 'src/features.dart';
import 'src/globals.dart' as globals;
import 'src/runner/flutter_command.dart';
import 'src/web/compile.dart';
......@@ -98,7 +99,13 @@ Future<void> main(List<String> args) async {
LogsCommand(),
MakeHostAppEditableCommand(),
PackagesCommand(),
PrecacheCommand(verboseHelp: verboseHelp),
PrecacheCommand(
verboseHelp: verboseHelp,
cache: globals.cache,
logger: globals.logger,
platform: globals.platform,
featureFlags: featureFlags,
),
RunCommand(verboseHelp: verboseHelp),
ScreenshotCommand(),
ShellCompletionCommand(),
......
......@@ -37,6 +37,7 @@ class DevelopmentArtifact {
/// Artifacts required for Android development.
static const DevelopmentArtifact androidGenSnapshot = DevelopmentArtifact._('android_gen_snapshot');
static const DevelopmentArtifact androidMaven = DevelopmentArtifact._('android_maven');
// Artifacts used for internal builds.
static const DevelopmentArtifact androidInternalBuild = DevelopmentArtifact._('android_internal_build');
......@@ -243,8 +244,8 @@ class Cache {
/// Checks if the current process owns the lock for the cache directory at
/// this very moment; throws a [StateError] if it doesn't.
static void checkLockAcquired() {
if (_lockEnabled && _lock == null && globals.platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') {
static void checkLockAcquired([Platform platform]) {
if (_lockEnabled && _lock == null && (platform ?? globals.platform).environment['FLUTTER_ALREADY_LOCKED'] != 'true') {
throw StateError(
'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 {
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) {
final File stampFile = getStampFileFor(artifactName);
return stampFile.existsSync() ? stampFile.readAsStringSync().trim() : null;
......@@ -508,6 +524,13 @@ abstract class ArtifactSet {
/// Updates the artifact.
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.
......@@ -520,11 +543,10 @@ abstract class CachedArtifact extends ArtifactSet {
final Cache cache;
/// The canonical name of the artifact.
@override
final String name;
// The name of the stamp file. Defaults to the same as the
// artifact name.
@override
String get stampName => name;
Directory get location => cache.getArtifactDirectory(name);
......@@ -1043,6 +1065,9 @@ class AndroidMavenArtifacts extends ArtifactSet {
// Therefore, call Gradle to figure this out.
return false;
}
@override
String get name => 'android-maven-artifacts';
}
class IOSEngineArtifacts extends EngineCachedArtifact {
......
......@@ -4,18 +4,36 @@
import 'dart:async';
import 'package:meta/meta.dart';
import '../base/common.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../cache.dart';
import '../features.dart';
import '../globals.dart' as globals;
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 {
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,
help: 'Precache artifacts for all host platforms.');
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,
help: 'Precache artifacts for Android development.',
hide: verboseHelp);
......@@ -48,6 +66,12 @@ class PrecacheCommand extends FlutterCommand {
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
final String name = 'precache';
......@@ -112,26 +136,29 @@ class PrecacheCommand extends FlutterCommand {
@override
Future<FlutterCommandResult> runCommand() async {
// Re-lock the cache.
if (globals.platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') {
if (_platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') {
await Cache.lock();
}
if (boolArg('force')) {
_cache.clearStampFiles();
}
final bool includeAllPlatforms = boolArg('all-platforms');
if (includeAllPlatforms) {
globals.cache.includeAllPlatforms = true;
_cache.includeAllPlatforms = true;
}
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 Set<DevelopmentArtifact> requiredArtifacts = <DevelopmentArtifact>{};
for (final DevelopmentArtifact artifact in DevelopmentArtifact.values) {
// Don't include unstable artifacts on stable branches.
if (!globals.flutterVersion.isMaster && artifact.unstable) {
if (!(_flutterVersion ?? globals.flutterVersion).isMaster && artifact.unstable) {
continue;
}
if (artifact.feature != null && !featureFlags.isEnabled(artifact.feature)) {
if (artifact.feature != null && !_featureFlags.isEnabled(artifact.feature)) {
continue;
}
......@@ -140,11 +167,10 @@ class PrecacheCommand extends FlutterCommand {
requiredArtifacts.add(artifact);
}
}
final bool forceUpdate = boolArg('force');
if (forceUpdate || !globals.cache.isUpToDate()) {
await globals.cache.updateAll(requiredArtifacts);
if (!_cache.isUpToDate()) {
await _cache.updateAll(requiredArtifacts);
} else {
globals.printStatus('Already up-to-date.');
_logger.printStatus('Already up-to-date.');
}
return FlutterCommandResult.success();
}
......
......@@ -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/io.dart' show InternetAddress, SocketException;
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/os.dart';
import 'package:flutter_tools/src/base/platform.dart';
......@@ -241,13 +242,13 @@ void main() {
});
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();
expect(() => cache.storageBaseUrl, throwsToolExit());
}, overrides: <Type, Generator>{
Platform: () => MockPlatform(),
Platform: () => FakePlatform(environment: <String, String>{
'FLUTTER_STORAGE_BASE_URL': ' http://foo',
}),
});
});
......@@ -624,6 +625,83 @@ void main() {
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 {
......@@ -689,7 +767,6 @@ class MockIosUsbArtifacts extends Mock implements IosUsbArtifacts {}
class MockInternetAddress extends Mock implements InternetAddress {}
class MockCache extends Mock implements Cache {}
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
class MockPlatform extends Mock implements Platform {}
class MockVersionedPackageResolver extends Mock implements VersionedPackageResolver {}
class MockHttpClientRequest extends Mock implements HttpClientRequest {}
......
......@@ -934,4 +934,7 @@ class FakeCache implements Cache {
Future<bool> doesRemoteExist(String message, Uri url) async {
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