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(); - }); -}