Don't rewrite package_config and package_graph if unchanged (#4549)

diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 36719a1..0f9779b 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -392,6 +392,9 @@
   /// Writes the .dart_tool/package_config.json file and workspace references to
   /// it.
   ///
+  /// Compares it to the existing .dart_tool/package_config.json and does not
+  /// rewrite it unless it is
+  ///
   /// Also writes the .dart_tool.package_graph.json file.
   ///
   /// If the workspace is non-trivial: For each package in the workspace write:
@@ -399,7 +402,8 @@
   /// package dir.
   Future<void> writePackageConfigFiles() async {
     ensureDir(p.dirname(packageConfigPath));
-    writeTextFile(
+
+    _writeIfDifferent(
       packageConfigPath,
       await _packageConfigFile(
         cache,
@@ -410,7 +414,8 @@
                 ?.effectiveConstraint,
       ),
     );
-    writeTextFile(packageGraphPath, await _packageGraphFile(cache));
+    _writeIfDifferent(packageGraphPath, await _packageGraphFile(cache));
+
     if (workspaceRoot.workspaceChildren.isNotEmpty) {
       for (final package in workspaceRoot.transitiveWorkspace) {
         final workspaceRefDir = p.join(package.dir, '.dart_tool', 'pub');
@@ -501,7 +506,6 @@
     final packageConfig = PackageConfig(
       configVersion: 2,
       packages: entries,
-      generated: DateTime.now(),
       generator: 'pub',
       generatorVersion: sdk.version,
       additionalProperties: {
@@ -520,6 +524,17 @@
     return '$jsonText\n';
   }
 
+  void _writeIfDifferent(String path, String newContent) {
+    // Compare to the present package_config.json
+    // For purposes of equality we don't care about the `generated` timestamp.
+    final originalText = tryReadTextFile(path);
+    if (originalText != newContent) {
+      writeTextFile(path, newContent);
+    } else {
+      log.fine('`$path` is unchanged. Not rewriting.');
+    }
+  }
+
   /// Gets all dependencies of the [workspaceRoot] package.
   ///
   /// Performs version resolution according to [SolveType].
diff --git a/lib/src/package_config.dart b/lib/src/package_config.dart
index 408d876..c5d330b 100644
--- a/lib/src/package_config.dart
+++ b/lib/src/package_config.dart
@@ -19,11 +19,6 @@
   /// Packages configured.
   List<PackageConfigEntry> packages;
 
-  /// Date-time the `.dart_tool/package_config.json` file was generated.
-  ///
-  /// `null` if not given.
-  DateTime? generated;
-
   /// Tool that generated the `.dart_tool/package_config.json` file.
   ///
   /// For `pub` this is always `'pub'`.
@@ -46,7 +41,6 @@
   PackageConfig({
     required this.configVersion,
     required this.packages,
-    this.generated,
     this.generator,
     this.generatorVersion,
     Map<String, dynamic>? additionalProperties,
@@ -98,16 +92,6 @@
       packages.add(PackageConfigEntry.fromJson(entry as Object));
     }
 
-    // Read the 'generated' property
-    DateTime? generated;
-    final generatedRaw = root['generated'];
-    if (generatedRaw != null) {
-      if (generatedRaw is! String) {
-        throwFormatException('generated', 'must be a string, if given');
-      }
-      generated = DateTime.parse(generatedRaw);
-    }
-
     // Read the 'generator' property
     final generator = root['generator'];
     if (generator is! String?) {
@@ -136,7 +120,6 @@
     return PackageConfig(
       configVersion: configVersion,
       packages: packages,
-      generated: generated,
       generator: generator,
       generatorVersion: generatorVersion,
       additionalProperties: Map.fromEntries(
@@ -158,7 +141,6 @@
   Map<String, Object?> toJson() => {
     'configVersion': configVersion,
     'packages': packages.map((p) => p.toJson()).toList(),
-    'generated': generated?.toUtc().toIso8601String(),
     'generator': generator,
     'generatorVersion': generatorVersion?.toString(),
   }..addAll(additionalProperties);
diff --git a/test/descriptor/package_config.dart b/test/descriptor/package_config.dart
index fb7996d..8aad157 100644
--- a/test/descriptor/package_config.dart
+++ b/test/descriptor/package_config.dart
@@ -28,7 +28,6 @@
       packages: _packages,
       generatorVersion: Version.parse(_generatorVersion),
       generator: 'pub',
-      generated: DateTime.now().toUtc(),
       additionalProperties: {
         'pubCache': p.toUri(_pubCache).toString(),
         if (_flutterRoot != null)
@@ -94,9 +93,6 @@
     );
 
     final expected = PackageConfig.fromJson(_config.toJson());
-    // omit generated date-time and packages
-    expected.generated = null; // comparing timestamps is unnecessary.
-    config.generated = null;
     expected.packages = []; // Already compared packages (ignoring ordering)
     config.packages = [];
     expect(
diff --git a/test/package_config_file_test.dart b/test/package_config_file_test.dart
index c4c59c0..b924c22 100644
--- a/test/package_config_file_test.dart
+++ b/test/package_config_file_test.dart
@@ -2,10 +2,14 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:convert';
+import 'dart:io';
+
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/package_config.dart';
 import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart';
 
 import 'descriptor.dart' as d;
 import 'test_pub.dart';
@@ -308,4 +312,43 @@
       ]).validate();
     });
   });
+
+  test(
+    'package_config and package_graph are not rewritten if unchanged',
+    () async {
+      final server = await servePackages();
+      server.serve('foo', '1.0.0');
+
+      await d.appDir(dependencies: {'foo': 'any'}).create();
+
+      await pubGet();
+      final packageConfigFile = File(
+        p.join(sandbox, appPath, '.dart_tool', 'package_config.json'),
+      );
+      final packageConfig = jsonDecode(packageConfigFile.readAsStringSync());
+      final packageConfigTimestamp = packageConfigFile.lastModifiedSync();
+      final packageGraphFile = File(
+        p.join(sandbox, appPath, '.dart_tool', 'package_graph.json'),
+      );
+      final packageGraph = jsonDecode(packageGraphFile.readAsStringSync());
+      final packageGraphTimestamp = packageGraphFile.lastModifiedSync();
+      final s = p.separator;
+      await pubGet(
+        silent: allOf(
+          contains(
+            '`.dart_tool${s}package_config.json` is unchanged. Not rewriting.',
+          ),
+          contains(
+            '`.dart_tool${s}package_graph.json` is unchanged. Not rewriting.',
+          ),
+        ),
+      );
+
+      expect(packageConfig, jsonDecode(packageConfigFile.readAsStringSync()));
+      expect(packageConfigFile.lastModifiedSync(), packageConfigTimestamp);
+
+      expect(packageGraph, jsonDecode(packageGraphFile.readAsStringSync()));
+      expect(packageGraphFile.lastModifiedSync(), packageGraphTimestamp);
+    },
+  );
 }
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 58cf843..41f85c0 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
@@ -122,7 +122,6 @@
 [E]    |   "languageVersion": "3.0"
 [E]    |   }
 [E]    |   ],
-[E]    |   "generated": "$TIME",
 [E]    |   "generator": "pub",
 [E]    |   "generatorVersion": "3.1.2+3",
 [E]    |   "pubCache": "file://$SANDBOX/cache"
@@ -309,7 +308,6 @@
    |   "languageVersion": "3.0"
    |   }
    |   ],
-   |   "generated": "$TIME",
    |   "generator": "pub",
    |   "generatorVersion": "3.1.2+3",
    |   "pubCache": "file://$SANDBOX/cache"