Avoid packageGraph (and therefore getExecutableForCommand) depending on the lock-file (#3533)

diff --git a/lib/src/command/deps.dart b/lib/src/command/deps.dart
index 678aafa..c860f02 100644
--- a/lib/src/command/deps.dart
+++ b/lib/src/command/deps.dart
@@ -102,8 +102,7 @@
                     ? 'dev'
                     : 'transitive'));
         final source =
-            entrypoint.packageGraph.lockFile.packages[current]?.source.name ??
-                'root';
+            entrypoint.lockFile.packages[current]?.source.name ?? 'root';
         packagesJson.add({
           'name': current,
           'version': currentPackage.version.toString(),
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index eb643fe..d250cc0 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -107,6 +107,34 @@
 
   LockFile? _lockFile;
 
+  /// The `.dart_tool/package_config.json` package-config of this entrypoint.
+  ///
+  /// Lazily initialized. Will throw [DataError] when initializing if the
+  /// `.dart_tool/packageConfig.json` file doesn't exist or has a bad format .
+  late PackageConfig packageConfig = () {
+    late String packageConfigRaw;
+    try {
+      packageConfigRaw = readTextFile(packageConfigPath);
+    } on FileException {
+      dataError(
+          'The "$packageConfigPath" file does not exist, please run "$topLevelProgram pub get".');
+    }
+    late PackageConfig result;
+    try {
+      result = PackageConfig.fromJson(json.decode(packageConfigRaw));
+    } on FormatException {
+      badPackageConfig();
+    }
+    // Version 2 is the initial version number for `package_config.json`,
+    // because `.packages` was version 1 (even if it was a different file).
+    // If the version is different from 2, then it must be a newer incompatible
+    // version, hence, the user should run `pub get` with the downgraded SDK.
+    if (result.configVersion != 2) {
+      badPackageConfig();
+    }
+    return result;
+  }();
+
   /// The package graph for the application and all of its transitive
   /// dependencies.
   ///
@@ -117,11 +145,16 @@
   PackageGraph _createPackageGraph() {
     assertUpToDate();
     var packages = {
-      for (var id in lockFile.packages.values) id.name: cache.load(id)
+      for (var packageEntry in packageConfig.nonInjectedPackages)
+        packageEntry.name: Package.load(
+          packageEntry.name,
+          packageEntry.resolvedRootDir(packageConfigPath),
+          cache.sources,
+        ),
     };
     packages[root.name] = root;
 
-    return PackageGraph(this, lockFile, packages);
+    return PackageGraph(this, packages);
   }
 
   PackageGraph? _packageGraph;
@@ -139,9 +172,10 @@
   /// not require it or make use of it within pub.
   String get packagesFile => p.normalize(p.join(_configRoot!, '.packages'));
 
-  /// The path to the entrypoint's ".dart_tool/package_config.json" file.
-  String get packageConfigFile =>
-      p.normalize(p.join(_configRoot!, '.dart_tool', 'package_config.json'));
+  /// The path to the entrypoint's ".dart_tool/package_config.json" file
+  /// relative to the current working directory .
+  late String packageConfigPath = p.relative(
+      p.normalize(p.join(_configRoot!, '.dart_tool', 'package_config.json')));
 
   /// The path to the entrypoint package's pubspec.
   String get pubspecPath => p.normalize(root.path('pubspec.yaml'));
@@ -226,9 +260,9 @@
   /// Writes .packages and .dart_tool/package_config.json
   Future<void> writePackageConfigFile() async {
     final entrypointName = isGlobal ? null : root.name;
-    ensureDir(p.dirname(packageConfigFile));
+    ensureDir(p.dirname(packageConfigPath));
     writeTextFile(
-      packageConfigFile,
+      packageConfigPath,
       await lockFile.packageConfigFile(
         cache,
         entrypoint: entrypointName,
@@ -421,7 +455,7 @@
         executablePath: resolveExecutable(executable),
         outputPath: pathOfExecutable(executable),
         incrementalDillPath: incrementalDillPathOfExecutable(executable),
-        packageConfigPath: packageConfigFile,
+        packageConfigPath: packageConfigPath,
         name:
             '$package:${p.basenameWithoutExtension(executable.relativePath)}');
   }
@@ -512,9 +546,9 @@
       dataError(
           'No $lockFilePath file found, please run "$topLevelProgram pub get" first.');
     }
-    if (!entryExists(packageConfigFile)) {
+    if (!entryExists(packageConfigPath)) {
       dataError(
-        'No $packageConfigFile file found, please run "$topLevelProgram pub get".\n'
+        'No $packageConfigPath file found, please run "$topLevelProgram pub get".\n'
         '\n'
         'Starting with Dart 2.7, the package_config.json file configures '
         'resolution of package import URIs; run "$topLevelProgram pub get" to generate it.',
@@ -559,7 +593,7 @@
       }
     }
 
-    var packageConfigModified = File(packageConfigFile).lastModifiedSync();
+    var packageConfigModified = File(packageConfigPath).lastModifiedSync();
     if (packageConfigModified.isBefore(lockFileModified) ||
         hasPathDependencies) {
       // If `package_config.json` is older than `pubspec.lock` or we have
@@ -568,9 +602,9 @@
       //  * Mitigate issues when copying a folder from one machine to another.
       //  * Force `pub get` if a path dependency has changed language version.
       _checkPackageConfigUpToDate();
-      touch(packageConfigFile);
+      touch(packageConfigPath);
     } else if (touchedLockFile) {
-      touch(packageConfigFile);
+      touch(packageConfigPath);
     }
 
     for (var match in _sdkConstraint.allMatches(lockFileText)) {
@@ -724,49 +758,13 @@
   void _checkPackageConfigUpToDate() {
     void outOfDate() {
       dataError('The $lockFilePath file has changed since the '
-          '$packageConfigFile file '
+          '$packageConfigPath file '
           'was generated, please run "$topLevelProgram pub get" again.');
     }
 
-    void badPackageConfig() {
-      dataError('The "$packageConfigFile" file is not recognized by '
-          '"pub" version, please run "$topLevelProgram pub get".');
-    }
-
-    late String packageConfigRaw;
-    try {
-      packageConfigRaw = readTextFile(packageConfigFile);
-    } on FileException {
-      dataError(
-          'The "$packageConfigFile" file does not exist, please run "$topLevelProgram pub get".');
-    }
-
-    late PackageConfig cfg;
-    try {
-      cfg = PackageConfig.fromJson(json.decode(packageConfigRaw));
-    } on FormatException {
-      badPackageConfig();
-    }
-
-    // Version 2 is the initial version number for `package_config.json`,
-    // because `.packages` was version 1 (even if it was a different file).
-    // If the version is different from 2, then it must be a newer incompatible
-    // version, hence, the user should run `pub get` with the downgraded SDK.
-    if (cfg.configVersion != 2) {
-      badPackageConfig();
-    }
-
     final packagePathsMapping = <String, String>{};
 
-    // We allow the package called 'flutter_gen' to be injected into
-    // package_config.
-    //
-    // This is somewhat a hack. But it allows flutter to generate code in a
-    // package as it likes.
-    //
-    // See https://github.com/flutter/flutter/issues/73870 .
-    final packagesToCheck =
-        cfg.packages.where((package) => package.name != 'flutter_gen');
+    final packagesToCheck = packageConfig.nonInjectedPackages;
     for (final pkg in packagesToCheck) {
       // Pub always makes a packageUri of lib/
       if (pkg.packageUri == null || pkg.packageUri.toString() != 'lib/') {
@@ -781,7 +779,7 @@
 
     // Check if language version specified in the `package_config.json` is
     // correct. This is important for path dependencies as these can mutate.
-    for (final pkg in cfg.packages) {
+    for (final pkg in packageConfig.packages) {
       if (pkg.name == root.name || pkg.name == 'flutter_gen') continue;
       final id = lockFile.packages[pkg.name];
       if (id == null) {
@@ -909,6 +907,11 @@
       }
     }
   }
+
+  Never badPackageConfig() {
+    dataError('The "$packageConfigPath" file is not recognized by '
+        '"pub" version, please run "$topLevelProgram pub get".');
+  }
 }
 
 /// Returns `true` if the [text] looks like it uses windows line endings.
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index 5d8ea8b..d926023 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -106,7 +106,7 @@
   // helpful for the subprocess to be able to spawn Dart with
   // Platform.executableArguments and have that work regardless of the working
   // directory.
-  final packageConfigAbsolute = p.absolute(entrypoint.packageConfigFile);
+  final packageConfigAbsolute = p.absolute(entrypoint.packageConfigPath);
 
   try {
     return await _runDartProgram(
@@ -335,14 +335,13 @@
     command = package;
   }
 
-  if (!entrypoint.packageGraph.packages.containsKey(package)) {
+  if (!entrypoint.packageConfig.packages.any((p) => p.name == package)) {
     throw CommandResolutionFailedException._(
       'Could not find package `$package` or file `$descriptor`',
       CommandResolutionIssue.packageNotFound,
     );
   }
   final executable = Executable(package, p.join('bin', '$command.dart'));
-  final packageConfig = p.join('.dart_tool', 'package_config.json');
 
   final path = entrypoint.resolveExecutable(executable);
   if (!fileExists(path)) {
@@ -351,10 +350,12 @@
       CommandResolutionIssue.noBinaryFound,
     );
   }
+  final packageConfigPath =
+      p.relative(entrypoint.packageConfigPath, from: root);
   if (!allowSnapshot) {
     return DartExecutableWithPackageConfig(
       executable: p.relative(path, from: root),
-      packageConfig: packageConfig,
+      packageConfig: packageConfigPath,
     );
   } else {
     final snapshotPath = entrypoint.pathOfExecutable(executable);
@@ -373,7 +374,7 @@
     }
     return DartExecutableWithPackageConfig(
       executable: p.relative(snapshotPath, from: root),
-      packageConfig: packageConfig,
+      packageConfig: packageConfigPath,
     );
   }
 }
diff --git a/lib/src/package_config.dart b/lib/src/package_config.dart
index e5e5e7d..dec8b5c 100644
--- a/lib/src/package_config.dart
+++ b/lib/src/package_config.dart
@@ -4,6 +4,7 @@
 
 import 'dart:convert';
 
+import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 
 import 'language_version.dart';
@@ -139,6 +140,16 @@
         'generator': generator,
         'generatorVersion': generatorVersion?.toString(),
       }..addAll(additionalProperties);
+
+  // We allow the package called 'flutter_gen' to be injected into
+  // package_config.
+  //
+  // This is somewhat a hack. But it allows flutter to generate code in a
+  // package as it likes.
+  //
+  // See https://github.com/flutter/flutter/issues/73870 .
+  Iterable<PackageConfigEntry> get nonInjectedPackages =>
+      packages.where((package) => package.name != 'flutter_gen');
 }
 
 class PackageConfigEntry {
@@ -260,4 +271,8 @@
     // TODO: implement toString
     return JsonEncoder.withIndent('  ').convert(toJson());
   }
+
+  String resolvedRootDir(String packageConfigPath) {
+    return p.join(p.dirname(packageConfigPath), p.fromUri(rootUri));
+  }
 }
diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart
index cbd91ec..6c736e6 100644
--- a/lib/src/package_graph.dart
+++ b/lib/src/package_graph.dart
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:collection/collection.dart' hide mapMap;
+import 'package:path/path.dart' as p;
 
 import 'entrypoint.dart';
-import 'lock_file.dart';
 import 'package.dart';
 import 'solver.dart';
-import 'source/cached.dart';
 import 'utils.dart';
 
 /// A holistic view of the entire transitive dependency graph for an entrypoint.
@@ -16,12 +15,6 @@
   /// The entrypoint.
   final Entrypoint entrypoint;
 
-  /// The entrypoint's lockfile.
-  ///
-  /// This describes the sources and resolved descriptions of everything in
-  /// [packages].
-  final LockFile lockFile;
-
   /// The transitive dependencies of the entrypoint (including itself).
   ///
   /// This may not include all transitive dependencies of the entrypoint if the
@@ -32,7 +25,7 @@
   /// A map of transitive dependencies for each package.
   Map<String, Set<Package>>? _transitiveDependencies;
 
-  PackageGraph(this.entrypoint, this.lockFile, this.packages);
+  PackageGraph(this.entrypoint, this.packages);
 
   /// Creates a package graph using the data from [result].
   ///
@@ -50,7 +43,7 @@
               )
     };
 
-    return PackageGraph(entrypoint, result.lockFile, packages);
+    return PackageGraph(entrypoint, packages);
   }
 
   /// Returns all transitive dependencies of [package].
@@ -81,12 +74,7 @@
     // the entrypoint.
     // TODO(sigurdm): there should be a way to get the id of any package
     // including the root.
-    if (package == entrypoint.root.name) {
-      return entrypoint.isCached;
-    } else {
-      var id = lockFile.packages[package]!;
-      return id.source is CachedSource;
-    }
+    return p.isWithin(entrypoint.cache.rootDir, packages[package]!.dir);
   }
 
   /// Returns whether [package] is mutable.
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index d06b025..0b6cd5b 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -301,7 +301,6 @@
       {String? expectedName, bool allowOverridesFile = false}) {
     var pubspecPath = path.join(packageDir, pubspecYamlFilename);
     var overridesPath = path.join(packageDir, pubspecOverridesFilename);
-
     if (!fileExists(pubspecPath)) {
       throw FileException(
           // Make the package dir absolute because for the entrypoint it'll just
diff --git a/test/global/run/runs_script_without_packages_file_test.dart b/test/global/run/runs_script_without_packages_file_test.dart
deleted file mode 100644
index dc225ba..0000000
--- a/test/global/run/runs_script_without_packages_file_test.dart
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
-// 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 'package:path/path.dart' as p;
-import 'package:pub/src/io.dart';
-import 'package:test/test.dart';
-
-import '../../descriptor.dart' as d;
-import '../../test_pub.dart';
-
-void main() {
-  test('runs a snapshotted script without a .dart_tool/package_config file',
-      () async {
-    final server = await servePackages();
-    server.serve('foo', '1.0.0', contents: [
-      d.dir('bin', [d.file('script.dart', "main(args) => print('ok');")])
-    ]);
-
-    await runPub(args: ['global', 'activate', 'foo']);
-
-    // Mimic the global packages installed by pub <1.12, which didn't create a
-    // .packages file for global installs.
-    deleteEntry(p.join(d.sandbox, cachePath,
-        'global_packages/foo/.dart_tool/package_config.json'));
-
-    var pub = await pubRun(global: true, args: ['foo:script']);
-    expect(pub.stdout, emitsThrough('ok'));
-    await pub.shouldExit();
-  });
-
-  test(
-      'runs an unsnapshotted script without a .dart_tool/package_config.json file',
-      () async {
-    await d.dir('foo', [
-      d.libPubspec('foo', '1.0.0'),
-      d.dir('bin', [d.file('foo.dart', "main() => print('ok');")])
-    ]).create();
-
-    await runPub(args: ['global', 'activate', '--source', 'path', '../foo']);
-
-    deleteEntry(p.join(d.sandbox, cachePath,
-        'global_packages/foo/.dart_tool/package_config.json'));
-
-    var pub = await pubRun(global: true, args: ['foo']);
-    expect(pub.stdout, emitsThrough('ok'));
-    await pub.shouldExit();
-  });
-}