Add a README.md to the pub cache after command ends (#3650)
diff --git a/lib/src/command/cache_clean.dart b/lib/src/command/cache_clean.dart
index 42b0f1f..786707d 100644
--- a/lib/src/command/cache_clean.dart
+++ b/lib/src/command/cache_clean.dart
@@ -32,8 +32,7 @@
You will have to run `$topLevelProgram pub get` again in each project.
Are you sure?''')) {
log.message('Removing pub cache directory ${cache.rootDir}.');
- deleteEntry(cache.rootDir);
- ensureDir(cache.rootDir);
+ cache.clean();
}
} else {
log.message('No pub cache at ${cache.rootDir}.');
diff --git a/lib/src/source/cached.dart b/lib/src/source/cached.dart
index 54630c7..f847c87 100644
--- a/lib/src/source/cached.dart
+++ b/lib/src/source/cached.dart
@@ -55,7 +55,10 @@
dirExists(getDirectoryInCache(id, cache));
/// Downloads the package identified by [id] to the system cache.
- Future<PackageId> downloadToSystemCache(PackageId id, SystemCache cache);
+ Future<DownloadPackageResult> downloadToSystemCache(
+ PackageId id,
+ SystemCache cache,
+ );
/// Returns the [Package]s that have been downloaded to the system cache.
List<Package> getCachedPackages(SystemCache cache);
@@ -86,3 +89,14 @@
required this.success,
});
}
+
+class DownloadPackageResult {
+ /// The resolved package.
+ final PackageId packageId;
+
+ /// Whether we had to make changes in the cache in order to download the
+ /// package.
+ final bool didUpdate;
+
+ DownloadPackageResult(this.packageId, {required this.didUpdate});
+}
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 24b1294..4bef7fa 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -298,11 +298,12 @@
/// itself; each of the commit-specific directories are clones of a directory
/// in `cache/`.
@override
- Future<PackageId> downloadToSystemCache(
+ Future<DownloadPackageResult> downloadToSystemCache(
PackageId id,
SystemCache cache,
) async {
return await _pool.withResource(() async {
+ bool didUpdate = false;
final ref = id.toRef();
final description = ref.description;
if (description is! GitDescription) {
@@ -317,7 +318,7 @@
final resolvedRef =
(id.description as GitResolvedDescription).resolvedRef;
- await _ensureRevision(ref, resolvedRef, cache);
+ didUpdate |= await _ensureRevision(ref, resolvedRef, cache);
var revisionCachePath = _revisionCachePath(id, cache);
final path = description.path;
@@ -326,11 +327,12 @@
await _clone(_repoCachePath(ref, cache), revisionCachePath);
await _checkOut(revisionCachePath, resolvedRef);
_writePackageList(revisionCachePath, [path]);
+ didUpdate = true;
} else {
- _updatePackageList(revisionCachePath, path);
+ didUpdate |= _updatePackageList(revisionCachePath, path);
}
});
- return id;
+ return DownloadPackageResult(id, didUpdate: didUpdate);
});
}
@@ -425,13 +427,15 @@
/// Ensures that the canonical clone of the repository referred to by [ref]
/// contains the given Git [revision].
- Future _ensureRevision(
+ ///
+ /// Returns `true` if it had to update anything.
+ Future<bool> _ensureRevision(
PackageRef ref,
String revision,
SystemCache cache,
) async {
var path = _repoCachePath(ref, cache);
- if (_updatedRepos.contains(path)) return;
+ if (_updatedRepos.contains(path)) return false;
await _deleteGitRepoIfInvalid(path);
@@ -443,28 +447,33 @@
await _firstRevision(path, revision);
} on git.GitException catch (_) {
await _updateRepoCache(ref, cache);
+ return true;
}
+ return false;
}
/// Ensures that the canonical clone of the repository referred to by [ref]
/// exists and is up-to-date.
- Future _ensureRepoCache(PackageRef ref, SystemCache cache) async {
+ ///
+ /// Returns `true` if it had to update anything.
+ Future<bool> _ensureRepoCache(PackageRef ref, SystemCache cache) async {
var path = _repoCachePath(ref, cache);
- if (_updatedRepos.contains(path)) return;
+ if (_updatedRepos.contains(path)) return false;
await _deleteGitRepoIfInvalid(path);
if (!entryExists(path)) {
await _createRepoCache(ref, cache);
+ return true;
} else {
- await _updateRepoCache(ref, cache);
+ return await _updateRepoCache(ref, cache);
}
}
/// Creates the canonical clone of the repository referred to by [ref].
///
/// This assumes that the canonical clone doesn't yet exist.
- Future _createRepoCache(PackageRef ref, SystemCache cache) async {
+ Future<void> _createRepoCache(PackageRef ref, SystemCache cache) async {
final description = ref.description;
if (description is! GitDescription) {
throw ArgumentError('Wrong source');
@@ -484,14 +493,17 @@
/// [ref].
///
/// This assumes that the canonical clone already exists.
- Future _updateRepoCache(
+ ///
+ /// Returns `true` if it had to update anything.
+ Future<bool> _updateRepoCache(
PackageRef ref,
SystemCache cache,
) async {
var path = _repoCachePath(ref, cache);
- if (_updatedRepos.contains(path)) return Future.value();
+ if (_updatedRepos.contains(path)) return false;
await git.run(['fetch'], workingDir: path);
_updatedRepos.add(path);
+ return true;
}
/// Clean-up [dirPath] if it's an invalid git repository.
@@ -523,11 +535,14 @@
/// Updates the package list file in [revisionCachePath] to include [path], if
/// necessary.
- void _updatePackageList(String revisionCachePath, String path) {
+ ///
+ /// Returns `true` if it had to update anything.
+ bool _updatePackageList(String revisionCachePath, String path) {
var packages = _readPackageList(revisionCachePath);
- if (packages.contains(path)) return;
+ if (packages.contains(path)) return false;
_writePackageList(revisionCachePath, packages..add(path));
+ return true;
}
/// Returns the list of packages in [revisionCachePath].
@@ -571,7 +586,7 @@
///
/// If [shallow] is true, creates a shallow clone that contains no history
/// for the repository.
- Future _clone(
+ Future<void> _clone(
String from,
String to, {
bool mirror = false,
@@ -594,7 +609,7 @@
}
/// Checks out the reference [ref] in [repoPath].
- Future _checkOut(String repoPath, String ref) {
+ Future<void> _checkOut(String repoPath, String ref) {
return git
.run(['checkout', ref], workingDir: repoPath).then((result) => null);
}
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 1cb05d6..2d481e4 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -728,8 +728,9 @@
/// `pub get` with a filled cache to be a fast case that doesn't require any
/// new version-listings.
@override
- Future<PackageId> downloadToSystemCache(
+ Future<DownloadPackageResult> downloadToSystemCache(
PackageId id, SystemCache cache) async {
+ var didUpdate = false;
final packageDir = getDirectoryInCache(id, cache);
// Use the content-hash from the version-info to compare with what we
@@ -779,17 +780,20 @@
if (dirExists(packageDir)) {
contentHash ??= sha256FromCache(id, cache);
} else {
+ didUpdate = true;
if (cache.isOffline) {
fail(
'Missing package ${id.name}-${id.version}. Try again without --offline.');
}
contentHash = await _download(id, packageDir, cache);
}
- return PackageId(
- id.name,
- id.version,
- (id.description as ResolvedHostedDescription).withSha256(contentHash),
- );
+ return DownloadPackageResult(
+ PackageId(
+ id.name,
+ id.version,
+ (id.description as ResolvedHostedDescription).withSha256(contentHash),
+ ),
+ didUpdate: didUpdate);
}
/// Determines if the package identified by [id] is already downloaded to the
diff --git a/lib/src/system_cache.dart b/lib/src/system_cache.dart
index 11a10a1..2956626 100644
--- a/lib/src/system_cache.dart
+++ b/lib/src/system_cache.dart
@@ -227,10 +227,21 @@
Future<PackageId> downloadPackage(PackageId id) async {
final source = id.source;
assert(source is CachedSource);
- return await (source as CachedSource).downloadToSystemCache(
+ final result = await (source as CachedSource).downloadToSystemCache(
id,
this,
);
+
+ // We only update the README.md in the cache when a change to the cache has
+ // happened. This is:
+ // * to avoid failing if used with a read-only cache, and
+ // * because the cost of writing a single file is negligible compared to
+ // downloading a package, but might be significant in the fast-case where
+ // a the cache is already valid.
+ if (result.didUpdate) {
+ _ensureReadme();
+ }
+ return result.packageId;
}
/// Get the latest version of [package].
@@ -270,6 +281,51 @@
return latest;
}
+
+ /// Removes all contents of the system cache.
+ ///
+ /// Rewrites the README.md.
+ void clean() {
+ deleteEntry(rootDir);
+ ensureDir(rootDir);
+ _ensureReadme();
+ }
+
+ /// Write a README.md file in the root of the cache directory to document the
+ /// contents of the folder.
+ ///
+ /// This should only be called when we are doing another operation that is
+ /// modifying the `PUB_CACHE`. This ensures that users won't experience
+ /// 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, '''
+Pub Package Cache
+=================
+
+This folder is used by Pub to store cached packages used in Dart / Flutter
+projects.
+
+The contents of this folder should only be modified using the `dart pub` and
+`flutter pub` commands.
+
+Modifying this folder manually can lead to inconsistent behavior.
+
+For details on how manage the `PUB_CACHE`, see:
+https://dart.dev/go/pub-cache
+''');
+ } on Exception catch (e) {
+ // Failing to write the README.md should not disrupt other operations.
+ log.fine('Failed to write README.md in PUB_CACHE: $e');
+ }
+ }
+
+ bool _hasEnsuredReadme = false;
}
typedef SourceRegistry = Source Function(String? name);
diff --git a/test/cache/clean_test.dart b/test/cache/clean_test.dart
index 7a79bc8..b5909b5 100644
--- a/test/cache/clean_test.dart
+++ b/test/cache/clean_test.dart
@@ -23,11 +23,15 @@
await d.appDir({'foo': 'any', 'bar': 'any'}).create();
await pubGet();
final cache = path.join(d.sandbox, cachePath);
- expect(listDir(cache, includeHidden: true), isNotEmpty);
+ expect(listDir(cache, includeHidden: true), contains(endsWith('hosted')));
await runPub(
args: ['cache', 'clean', '--force'],
output: 'Removing pub cache directory $cache.');
- expect(listDir(cache, includeHidden: true), isEmpty);
+
+ expect(
+ listDir(cache, includeHidden: true),
+ // The README.md will be reconstructed.
+ [pathInCache('README.md')]);
});
test('running pub cache clean deletes cache only with confirmation',
@@ -38,7 +42,10 @@
await d.appDir({'foo': 'any', 'bar': 'any'}).create();
await pubGet();
final cache = path.join(d.sandbox, cachePath);
- expect(listDir(cache, includeHidden: true), isNotEmpty);
+ expect(
+ listDir(cache, includeHidden: true),
+ contains(pathInCache('hosted')),
+ );
{
final process = await startPub(
args: ['cache', 'clean'],
@@ -46,7 +53,10 @@
process.stdin.writeln('n');
expect(await process.exitCode, 0);
}
- expect(listDir(cache, includeHidden: true), isNotEmpty);
+ expect(
+ listDir(cache, includeHidden: true),
+ contains(pathInCache('hosted')),
+ );
{
final process = await startPub(
@@ -55,6 +65,9 @@
process.stdin.writeln('y');
expect(await process.exitCode, 0);
}
- expect(listDir(cache, includeHidden: true), isEmpty);
+ expect(
+ listDir(cache,
+ includeHidden: true), // The README.md will be reconstructed.
+ [pathInCache('README.md')]);
});
}
diff --git a/test/cache/create_readme_test.dart b/test/cache/create_readme_test.dart
new file mode 100644
index 0000000..035fd68
--- /dev/null
+++ b/test/cache/create_readme_test.dart
@@ -0,0 +1,37 @@
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+void main() async {
+ test('PUB_CACHE/README.md gets created by command downloading to pub cache',
+ () async {
+ final server = await servePackages();
+ server.serve('foo', '1.0.0');
+ await d.appDir().create();
+ await pubGet();
+ await d.nothing(cachePath).validate();
+
+ await d.appDir({'foo': '1.0.0'}).create();
+ await pubGet();
+ await d.dir(cachePath, [
+ d.file('README.md', contains('https://dart.dev/go/pub-cache'))
+ ]).validate();
+ File(pathInCache('README.md')).deleteSync();
+ // No new download, so 'README.md' doesn't get updated.
+ await pubGet();
+ await d.dir(cachePath, [d.nothing('README.md')]).validate();
+ });
+
+ test('PUB_CACHE/README.md gets created by `dart pub cache clean`', () async {
+ final server = await servePackages();
+ server.serve('foo', '1.0.0');
+ await d.appDir({'foo': '1.0.0'}).create();
+ await pubGet();
+ await d.dir(cachePath, [
+ d.file('README.md', contains('https://dart.dev/go/pub-cache'))
+ ]).validate();
+ });
+}
diff --git a/test/embedding/embedding_test.dart b/test/embedding/embedding_test.dart
index 4419c15..89b4d08 100644
--- a/test/embedding/embedding_test.dart
+++ b/test/embedding/embedding_test.dart
@@ -99,7 +99,7 @@
d.dir('bin', [
d.file('main.dart', '''
import 'dart:io';
-main() {
+main() {
print('Hi');
exit(123);
}
diff --git a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
index 219820e..f48d6ba 100644
--- a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
+++ b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt
@@ -72,6 +72,21 @@
[E] FINE: Extracted .tar.gz to $DIR
[E] IO : Renaming directory $A to $B
[E] IO : Deleting directory $DIR
+[E] IO : Writing $N characters to text file $SANDBOX/cache/README.md.
+[E] FINE: Contents:
+[E] | Pub Package Cache
+[E] | =================
+[E] |
+[E] | This folder is used by Pub to store cached packages used in Dart / Flutter
+[E] | projects.
+[E] |
+[E] | The contents of this folder should only be modified using the `dart pub` and
+[E] | `flutter pub` commands.
+[E] |
+[E] | Modifying this folder manually can lead to inconsistent behavior.
+[E] |
+[E] | For details on how manage the `PUB_CACHE`, see:
+[E] | https://dart.dev/go/pub-cache
[E] IO : Writing $N characters to text file pubspec.lock.
[E] FINE: Contents:
[E] | # Generated by pub
@@ -216,6 +231,21 @@
FINE: Extracted .tar.gz to $DIR
IO : Renaming directory $A to $B
IO : Deleting directory $DIR
+IO : Writing $N characters to text file $SANDBOX/cache/README.md.
+FINE: Contents:
+ | Pub Package Cache
+ | =================
+ |
+ | This folder is used by Pub to store cached packages used in Dart / Flutter
+ | projects.
+ |
+ | The contents of this folder should only be modified using the `dart pub` and
+ | `flutter pub` commands.
+ |
+ | Modifying this folder manually can lead to inconsistent behavior.
+ |
+ | For details on how manage the `PUB_CACHE`, see:
+ | https://dart.dev/go/pub-cache
MSG : + foo 1.0.0
IO : Writing $N characters to text file pubspec.lock.
FINE: Contents: