diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart index f491d69b8..16e2f1730 100644 --- a/lib/src/global_packages.dart +++ b/lib/src/global_packages.dart @@ -28,6 +28,7 @@ import 'source/git.dart'; import 'source/hosted.dart'; import 'source/path.dart'; import 'source/root.dart'; +import 'source/sdk.dart'; import 'system_cache.dart'; import 'utils.dart'; @@ -455,7 +456,16 @@ try: recompile: (exectuable) async { final root = entrypoint.workspaceRoot; final name = exectuable.package; - // Resolve it and download its dependencies. + + // When recompiling we re-resolve it and download its dependencies. This + // is mainly to protect from the case where the sdk was updated, and + // that causes some incompatibilities. (could be the new sdk is outside + // some package's environment constraint range, or that the sdk came + // with incompatible versions of sdk packages). + // + // We use --enforce-lockfile semantics, because we want upgrading + // globally activated packages to be conscious, and not a part of + // running them. SolveResult result; try { result = await log.spinner( @@ -474,14 +484,44 @@ Try reactivating the package. result.packages.removeWhere((id) => id.name == 'pub global activate'); final newLockFile = await result.downloadCachedPackages(cache); + final report = SolveReport( + SolveType.get, + entrypoint.workspaceRoot.dir, + entrypoint.workspaceRoot.pubspec, + entrypoint.workspaceRoot.allOverridesInWorkspace, + entrypoint.lockFile, + newLockFile, + result.availableVersions, + cache, + dryRun: true, + enforceLockfile: true, + quiet: false, + ); + await report.show(summary: true); + final sameVersions = entrypoint.lockFile.samePackageIds(newLockFile); + if (!sameVersions) { - dataError(''' -The package `$name` as currently activated cannot resolve to the same packages. + if (newLockFile.packages.values.any((p) { + return p.source is SdkSource && + p.version != entrypoint.lockFile.packages[p.name]?.version; + })) { + // More specific error message for the case of a version match with + // an sdk package. + dataError(''' +The current activation of `$name` is not compatible with your current SDK. Try reactivating the package. `$topLevelProgram pub global activate $name` '''); + } else { + dataError(''' +The current activation of `$name` cannot resolve to the same set of dependencies. + +Try reactivating the package. +`$topLevelProgram pub global activate $name` +'''); + } } await recompile(exectuable); _refreshBinStubs(entrypoint, executable); diff --git a/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart b/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart index dc308c7fa..01a03d6af 100644 --- a/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart +++ b/test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart @@ -48,6 +48,7 @@ void main() { // all output we see the precompilation messages as well. expect(pub.stdout, emits('Resolving dependencies...')); expect(pub.stdout, emits('Downloading packages...')); + expect(pub.stdout, emits(startsWith('No dependencies would change in '))); expect(pub.stdout, emits('Building package executable...')); expect(pub.stdout, emitsThrough('ok')); await pub.shouldExit(); @@ -66,6 +67,102 @@ void main() { test('validate resolution before recompilation', () async { final server = await servePackages(); + server.serve( + 'foo', + '1.0.0', + deps: { + 'bar': 'any', + }, + contents: [ + d.dir('bin', [ + d.file('foo.dart', 'import "package:bar/bar.dart"; main() => bar();'), + ]), + ], + ); + + server.serve( + 'bar', + '1.0.0', + contents: [ + d.dir('lib', [ + d.file('bar.dart', 'bar() => print("original");'), + ]), + ], + ); + + await runPub( + args: ['global', 'activate', 'foo'], + ); + + await runPub( + args: ['global', 'run', 'foo'], + output: 'original', + ); + + server.serve( + 'bar', + '1.0.0', + contents: [ + d.dir('lib', [ + d.file('foo.dart', 'foo() => print("updated");'), + ]), + ], + ); + + await runPub( + args: ['global', 'run', 'foo'], + environment: { + 'DART_ROOT': p.join(d.sandbox, 'dart'), + // Updated sdk version makes the old snapshot obsolete + '_PUB_TEST_SDK_VERSION': '3.2.1+4', + }, + output: contains('~ bar 1.0.0 (was 1.0.0)'), + error: allOf( + contains( + 'The current activation of `foo` cannot resolve to the same set of ' + 'dependencies.', + ), + contains( + "The existing content-hash from pubspec.lock doesn't match " + 'contents for:', + ), + contains('Try reactivating the package'), + ), + exitCode: DATA, + ); + + await d.dir('dart', [ + d.dir('packages', [ + d.dir('bar', [ + // Doesn't fulfill constraint, but doesn't satisfy pubspec.lock. + d.libPubspec('bar', '2.0.0', deps: {}), + ]), + ]), + ]).create(); + await runPub( + args: ['global', 'run', 'foo'], + environment: { + 'DART_ROOT': p.join(d.sandbox, 'dart'), + '_PUB_TEST_SDK_VERSION': '3.2.1+4', + }, + error: allOf( + contains( + 'The existing content-hash from pubspec.lock doesn\'t match ' + 'contents for:', + ), + contains( + 'The current activation of `foo` cannot resolve to the same ' + 'set of dependencies.', + ), + contains('Try reactivating the package'), + ), + exitCode: DATA, + ); + }); + + test('validate resolution before recompilation - updated sdk package', + () async { + final server = await servePackages(); server.serve( 'foo', '1.0.0', @@ -120,9 +217,12 @@ void main() { 'DART_ROOT': p.join(d.sandbox, 'dart'), '_PUB_TEST_SDK_VERSION': '3.2.1+4', }, + output: contains('> bar 1.2.0 from sdk dart (was 1.0.0 from sdk dart)'), error: allOf( - contains('The package `foo` as currently activated cannot resolve to ' - 'the same packages'), + contains( + 'The current activation of `foo` is not compatible with your ' + 'current SDK.', + ), contains('Try reactivating the package'), ), exitCode: DATA,