diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart index af3024ad9d8..b06568d3b39 100644 --- a/packages/flutter_tools/lib/src/commands/drive.dart +++ b/packages/flutter_tools/lib/src/commands/drive.dart @@ -154,6 +154,9 @@ class DriveCommand extends RunCommandBase { @override bool get startPausedDefault => true; + @override + bool get cachePubGet => false; + @override Future validateCommand() async { if (userIdentifier != null) { diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart index e9524f32916..3a4fc400e71 100644 --- a/packages/flutter_tools/lib/src/dart/pub.dart +++ b/packages/flutter_tools/lib/src/dart/pub.dart @@ -94,6 +94,7 @@ abstract class Pub { bool offline = false, bool generateSyntheticPackage = false, String flutterRootOverride, + bool checkUpToDate = false, }); /// Runs pub in 'batch' mode. @@ -118,7 +119,6 @@ abstract class Pub { bool showTraceForErrors, }); - /// Runs pub in 'interactive' mode. /// /// directly piping the stdin stream of this process to that of pub, and the @@ -164,12 +164,38 @@ class _DefaultPub implements Pub { bool offline = false, bool generateSyntheticPackage = false, String flutterRootOverride, + bool checkUpToDate = false, }) async { directory ??= _fileSystem.currentDirectory.path; final File packageConfigFile = _fileSystem.file( _fileSystem.path.join(directory, '.dart_tool', 'package_config.json')); final Directory generatedDirectory = _fileSystem.directory( _fileSystem.path.join(directory, '.dart_tool', 'flutter_gen')); + final File lastVersion = _fileSystem.file( + _fileSystem.path.join(directory, '.dart_tool', 'version')); + final File currentVersion = _fileSystem.file( + _fileSystem.path.join(Cache.flutterRoot, 'version')); + final File pubspecYaml = _fileSystem.file( + _fileSystem.path.join(directory, 'pubspec.yaml')); + final File pubLockFile = _fileSystem.file( + _fileSystem.path.join(directory, 'pubspec.lock') + ); + + // If the pubspec.yaml is older than the package config file and the last + // flutter version used is the same as the current version skip pub get. + // This will incorrectly skip pub on the master branch if dependencies + // are being added/removed from the flutter framework packages, but this + // can be worked around by manually running pub. + if (checkUpToDate && + packageConfigFile.existsSync() && + pubLockFile.existsSync() && + pubspecYaml.lastModifiedSync().isBefore(pubLockFile.lastModifiedSync()) && + pubspecYaml.lastModifiedSync().isBefore(packageConfigFile.lastModifiedSync()) && + lastVersion.existsSync() && + lastVersion.readAsStringSync() == currentVersion.readAsStringSync()) { + _logger.printTrace('Skipping pub get: version match.'); + return; + } final String command = upgrade ? 'upgrade' : 'get'; final Status status = _logger.startProgress( @@ -207,6 +233,7 @@ class _DefaultPub implements Pub { if (!packageConfigFile.existsSync()) { throwToolExit('$directory: pub did not create .dart_tools/package_config.json file.'); } + lastVersion.writeAsStringSync(currentVersion.readAsStringSync()); await _updatePackageConfig( packageConfigFile, generatedDirectory, diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index b0a752adb88..cd926a543bb 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -467,6 +467,9 @@ abstract class FlutterCommand extends Command { ); } + /// Whether it is safe for this command to use a cached pub invocation. + bool get cachePubGet => true; + Duration get deviceDiscoveryTimeout { if (_deviceDiscoveryTimeout == null && argResults.options.contains(FlutterOptions.kDeviceTimeout) @@ -1057,6 +1060,7 @@ abstract class FlutterCommand extends Command { await pub.get( context: PubContext.getVerifyContext(name), generateSyntheticPackage: project.manifest.generateSyntheticPackage, + checkUpToDate: cachePubGet, ); await project.regeneratePlatformSpecificTooling(); } diff --git a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart index 6919771558c..948a3e590c6 100644 --- a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart +++ b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart @@ -30,6 +30,269 @@ void main() { MockDirectory.findCache = false; }); + testWithoutContext('checkUpToDate skips pub get if the package config is newer than the pubspec ' + 'and the current framework version is the same as the last version', () async { + final FakeProcessManager processManager = FakeProcessManager.list([]); + final BufferLogger logger = BufferLogger.test(); + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + + fileSystem.file('pubspec.yaml').createSync(); + fileSystem.file('pubspec.lock').createSync(); + fileSystem.file('.dart_tool/package_config.json').createSync(recursive: true); + fileSystem.file('.dart_tool/version').writeAsStringSync('a'); + fileSystem.file('version').writeAsStringSync('a'); + + final Pub pub = Pub( + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + usage: MockUsage(), + platform: FakePlatform( + environment: const {}, + ), + botDetector: const BotDetectorAlwaysNo(), + ); + + await pub.get( + context: PubContext.pubGet, + checkUpToDate: true, + ); + + expect(logger.traceText, contains('Skipping pub get: version match.')); + }); + + testWithoutContext('checkUpToDate does not skip pub get if the package config is newer than the pubspec ' + 'but the current framework version is not the same as the last version', () async { + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand(command: [ + 'bin/cache/dart-sdk/bin/pub', + '--verbosity=warning', + 'get', + '--no-precompile', + ]) + ]); + final BufferLogger logger = BufferLogger.test(); + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + + fileSystem.file('pubspec.yaml').createSync(); + fileSystem.file('pubspec.lock').createSync(); + fileSystem.file('.dart_tool/package_config.json').createSync(recursive: true); + fileSystem.file('.dart_tool/version').writeAsStringSync('a'); + fileSystem.file('version').writeAsStringSync('b'); + + final Pub pub = Pub( + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + usage: MockUsage(), + platform: FakePlatform( + environment: const {}, + ), + botDetector: const BotDetectorAlwaysNo(), + ); + + await pub.get( + context: PubContext.pubGet, + checkUpToDate: true, + ); + + expect(processManager.hasRemainingExpectations, false); + expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b'); + }); + + testWithoutContext('checkUpToDate does not skip pub get if the package config is newer than the pubspec ' + 'but the current framework version does not exist yet', () async { + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand(command: [ + 'bin/cache/dart-sdk/bin/pub', + '--verbosity=warning', + 'get', + '--no-precompile', + ]) + ]); + final BufferLogger logger = BufferLogger.test(); + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + + fileSystem.file('pubspec.yaml').createSync(); + fileSystem.file('pubspec.lock').createSync(); + fileSystem.file('.dart_tool/package_config.json').createSync(recursive: true); + fileSystem.file('version').writeAsStringSync('b'); + + final Pub pub = Pub( + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + usage: MockUsage(), + platform: FakePlatform( + environment: const {}, + ), + botDetector: const BotDetectorAlwaysNo(), + ); + + await pub.get( + context: PubContext.pubGet, + checkUpToDate: true, + ); + + expect(processManager.hasRemainingExpectations, false); + expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b'); + }); + + testWithoutContext('checkUpToDate does not skip pub get if the package config does not exist', () async { + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.list([ + FakeCommand(command: const [ + 'bin/cache/dart-sdk/bin/pub', + '--verbosity=warning', + 'get', + '--no-precompile', + ], onRun: () { + fileSystem.file('.dart_tool/package_config.json').createSync(recursive: true); + }) + ]); + final BufferLogger logger = BufferLogger.test(); + + fileSystem.file('pubspec.yaml').createSync(); + fileSystem.file('pubspec.lock').createSync(); + fileSystem.file('version').writeAsStringSync('b'); + + final Pub pub = Pub( + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + usage: MockUsage(), + platform: FakePlatform( + environment: const {}, + ), + botDetector: const BotDetectorAlwaysNo(), + ); + + await pub.get( + context: PubContext.pubGet, + checkUpToDate: true, + ); + + expect(processManager.hasRemainingExpectations, false); + expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b'); + }); + + testWithoutContext('checkUpToDate does not skip pub get if the pubspec.lock does not exist', () async { + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand(command: [ + 'bin/cache/dart-sdk/bin/pub', + '--verbosity=warning', + 'get', + '--no-precompile', + ]), + ]); + final BufferLogger logger = BufferLogger.test(); + + fileSystem.file('pubspec.yaml').createSync(); + fileSystem.file('version').writeAsStringSync('b'); + fileSystem.file('.dart_tool/package_config.json').createSync(recursive: true); + fileSystem.file('.dart_tool/version').writeAsStringSync('b'); + + final Pub pub = Pub( + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + usage: MockUsage(), + platform: FakePlatform( + environment: const {}, + ), + botDetector: const BotDetectorAlwaysNo(), + ); + + await pub.get( + context: PubContext.pubGet, + checkUpToDate: true, + ); + + expect(processManager.hasRemainingExpectations, false); + expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b'); + }); + + testWithoutContext('checkUpToDate does not skip pub get if the package config is older that the pubspec', () async { + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand(command: [ + 'bin/cache/dart-sdk/bin/pub', + '--verbosity=warning', + 'get', + '--no-precompile', + ]) + ]); + final BufferLogger logger = BufferLogger.test(); + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + + fileSystem.file('pubspec.yaml').createSync(); + fileSystem.file('pubspec.lock').createSync(); + fileSystem.file('.dart_tool/package_config.json') + ..createSync(recursive: true) + ..setLastModifiedSync(DateTime(1991)); + fileSystem.file('version').writeAsStringSync('b'); + + final Pub pub = Pub( + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + usage: MockUsage(), + platform: FakePlatform( + environment: const {}, + ), + botDetector: const BotDetectorAlwaysNo(), + ); + + await pub.get( + context: PubContext.pubGet, + checkUpToDate: true, + ); + + expect(processManager.hasRemainingExpectations, false); + expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b'); + }); + + testWithoutContext('checkUpToDate does not skip pub get if the pubspec.lock is older that the pubspec', () async { + final FakeProcessManager processManager = FakeProcessManager.list([ + const FakeCommand(command: [ + 'bin/cache/dart-sdk/bin/pub', + '--verbosity=warning', + 'get', + '--no-precompile', + ]) + ]); + final BufferLogger logger = BufferLogger.test(); + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + + fileSystem.file('pubspec.yaml').createSync(); + fileSystem.file('pubspec.lock') + ..createSync() + ..setLastModifiedSync(DateTime(1991)); + fileSystem.file('.dart_tool/package_config.json') + .createSync(recursive: true); + fileSystem.file('version').writeAsStringSync('b'); + fileSystem.file('.dart_tool/version').writeAsStringSync('b'); + + final Pub pub = Pub( + fileSystem: fileSystem, + logger: logger, + processManager: processManager, + usage: MockUsage(), + platform: FakePlatform( + environment: const {}, + ), + botDetector: const BotDetectorAlwaysNo(), + ); + + await pub.get( + context: PubContext.pubGet, + checkUpToDate: true, + ); + + expect(processManager.hasRemainingExpectations, false); + expect(fileSystem.file('.dart_tool/version').readAsStringSync(), 'b'); + }); + testWithoutContext('pub get 69', () async { String error; @@ -209,6 +472,7 @@ void main() { } ), ); + fileSystem.file('version').createSync(); fileSystem.file('pubspec.yaml').createSync(); fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) @@ -237,6 +501,7 @@ void main() { } ), ); + fileSystem.file('version').createSync(); fileSystem.file('pubspec.yaml').createSync(); fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) @@ -377,6 +642,7 @@ void main() { botDetector: const BotDetectorAlwaysNo() ); + fileSystem.file('version').createSync(); // the good scenario: .packages is old, pub updates the file. fileSystem.file('.dart_tool/package_config.json') ..createSync(recursive: true) diff --git a/packages/flutter_tools/test/src/throwing_pub.dart b/packages/flutter_tools/test/src/throwing_pub.dart index 36656870f3b..41a150905a6 100644 --- a/packages/flutter_tools/test/src/throwing_pub.dart +++ b/packages/flutter_tools/test/src/throwing_pub.dart @@ -30,6 +30,7 @@ class ThrowingPub implements Pub { bool skipPubspecYamlCheck = false, bool generateSyntheticPackage = false, String flutterRootOverride, + bool checkUpToDate = false, }) { throw UnsupportedError('Attempted to invoke pub during test.'); }