Warn about presence of legacy cache (#3921)
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart index 4d19989..db3df6f 100644 --- a/lib/src/entrypoint.dart +++ b/lib/src/entrypoint.dart
@@ -517,6 +517,7 @@ additionalSources: additionalSources, nativeAssets: nativeAssets, ); + cache.maintainCache(); } /// The location of the snapshot of the dart program at [path] in [package]
diff --git a/lib/src/system_cache.dart b/lib/src/system_cache.dart index 90bd7d5..181e847 100644 --- a/lib/src/system_cache.dart +++ b/lib/src/system_cache.dart
@@ -256,7 +256,7 @@ // downloading a package, but might be significant in the fast-case where // a the cache is already valid. if (result.didUpdate) { - _ensureReadme(); + maintainCache(); } return result.packageId; } @@ -313,7 +313,83 @@ void clean() { deleteEntry(rootDir); ensureDir(rootDir); + maintainCache(); + } + + /// Tasks that ensures the cache is in a good condition. + /// Should be called whenever an operation updates the cache. + void maintainCache() { + /// We only want to do this once per run. + if (_hasMaintainedCache) return; + _hasMaintainedCache = true; _ensureReadme(); + _checkOldCacheLocation(); + } + + /// Check for the presence of a cache at the legacy location + /// `%APPDATA$\Pub\Cache`. + /// + /// If it is present, give a warning and write a DEPRECATED.md in that cache. + /// + /// If DEPRECATED.md is less than 7 days old, we don't repeat the warning. + void _checkOldCacheLocation() { + // Background: + // Prior to Dart 2.8 the default location for the PUB_CACHE on Windows was: + // %APPDATA%\Pub\Cache + // + // Start Dart 2.8 pub started migrating the default PUB_CACHE location to: + // %LOCALAPPDATA%\Pub\Cache + // That is: + // * If a pub-cache existed in `%LOCALAPPDATA%\Pub\Cache` then it + // would be used. + // * If a pub-cache existed in `%APPDATA%\Pub\Cache` then it would be + // used, unless a pub-cache in `%LOCALAPPDATA%\Pub\Cache` had been found. + // * If no pub-cache was found, a new empty pub-cache was created in + // `%LOCALAPPDATA%\Pub\Cache`. + // + // Starting in Dart 3.0 pub will no-longer look for a pub-cache in + // `%APPDATA%\Pub\Cache`. Instead it will always use the new location, + // `%LOCALAPPDATA%\Pub\Cache`, as default PUB_CACHE location. + // + // Using `%APPDATA%` caused the pub-cache to be copied with the user-profile, + // when using a networked Windows setup where users can login on multiple + // machines. This is undesirable because you are moving a lot of bytes over + // the network and onto whatever servers are storing the user profiles. + // + // Thus, we migrated to storing the pub-cache in `%LOCALAPPDATA%`. + // And finished the migration in Dart 3 to keep things simple. + if (!Platform.isWindows) return; + + final appData = Platform.environment['APPDATA']; + if (appData == null) return; + final legacyCacheLocation = p.join(appData, 'Pub', 'Cache'); + final legacyCacheDeprecatedFile = + p.join(legacyCacheLocation, 'DEPRECATED.md'); + final stat = tryStatFile(legacyCacheDeprecatedFile); + if ((stat == null || + DateTime.now().difference(stat.changed) > Duration(days: 7)) && + dirExists(legacyCacheLocation)) { + log.warning(''' +Found a legacy Pub cache at $legacyCacheLocation. Pub is using $defaultDir. + +Consider deleting the legacy cache. + +See https://dart.dev/resources/dart-3-migration#other-tools-changes for details. +'''); + try { + writeTextFile(legacyCacheDeprecatedFile, ''' +As of Dart 3 this pub cache is no longer used by Dart/Flutter. + +Consider deleting it, if you are not using Dart versions earlier than 2.8.0. + +See https://dart.dev/resources/dart-3-migration#other-tools-changes for details. +'''); + } on Exception catch (e) { + // Failing to write the DEPRECATED.md file should not disrupt other + // operations. + log.fine('Failed to write $legacyCacheDeprecatedFile: $e'); + } + } } /// Write a README.md file in the root of the cache directory to document the @@ -324,9 +400,6 @@ /// permission errors because we writing a `README.md` file, in a flow that /// the user expected wouldn't have issues with a read-only `PUB_CACHE`. void _ensureReadme() { - /// We only want to do this once per run. - if (_hasEnsuredReadme) return; - _hasEnsuredReadme = true; final readmePath = p.join(rootDir, 'README.md'); try { writeTextFile(readmePath, ''' @@ -350,7 +423,7 @@ } } - bool _hasEnsuredReadme = false; + bool _hasMaintainedCache = false; } typedef SourceRegistry = Source Function(String? name);
diff --git a/test/cache/create_readme_test.dart b/test/cache/create_readme_test.dart index 70a02a6..ffec057 100644 --- a/test/cache/create_readme_test.dart +++ b/test/cache/create_readme_test.dart
@@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:path/path.dart' as p; import 'package:test/test.dart'; import '../descriptor.dart' as d; @@ -34,4 +35,31 @@ d.file('README.md', contains('https://dart.dev/go/pub-cache')) ]).validate(); }); + + test('PUB_CACHE/README.md gets created when compiling a snapshot', () async { + final server = await servePackages(); + server.serve( + 'foo', + '1.0.0', + contents: [d.file('bin/foo.dart', "main() {print('Hello');}")], + ); + await runPub(args: ['global', 'activate', 'foo']); + File(p.join(d.sandbox, cachePath, 'README.md')).deleteSync(); + // Replace the created snapshot with one that really doesn't work with the + // current dart. + await d.dir(cachePath, [ + d.dir('global_packages', [ + d.dir('foo', [ + d.dir( + 'bin', + [d.outOfDateSnapshot('foo.dart-3.1.2+3.snapshot')], + ) + ]) + ]) + ]).create(); + await runPub(args: ['global', 'run', 'foo'], output: contains('Hello')); + await d.dir(cachePath, [ + d.file('README.md', contains('https://dart.dev/go/pub-cache')) + ]).validate(); + }); }
diff --git a/test/cache/detect_deprecated_dir_test.dart b/test/cache/detect_deprecated_dir_test.dart new file mode 100644 index 0000000..3320452 --- /dev/null +++ b/test/cache/detect_deprecated_dir_test.dart
@@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart'; + +import '../descriptor.dart' as d; +import '../test_pub.dart'; + +void main() async { + test('Detects and warns about old cache dir', skip: !Platform.isWindows, + () async { + await d.dir('APPDATA', [ + d.dir('Pub', [d.dir('Cache')]) + ]).create(); + final server = await servePackages(); + server.serve('foo', '1.0.0'); + await d.appDir(dependencies: {'foo': '^1.0.0'}).create(); + await pubGet( + warning: contains('Found a legacy Pub cache at'), + environment: {'APPDATA': d.path('APPDATA')}, + ); + expect( + File(p.join(sandbox, 'APPDATA', 'Pub', 'Cache', 'DEPRECATED.md')) + .existsSync(), + isTrue, + ); + server.serve('foo', '2.0.0'); + await d.appDir(dependencies: {'foo': '^2.0.0'}).create(); + await pubGet( + warning: isNot(contains('Found a legacy Pub cache')), + ); + }); +}