Skip to content

Commit

Permalink
Validate lockfile before recompiling executable
Browse files Browse the repository at this point in the history
  • Loading branch information
sigurdm committed Oct 10, 2024
1 parent 1efd3f5 commit 42017cf
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 31 deletions.
22 changes: 1 addition & 21 deletions lib/src/entrypoint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,6 @@ Try running `$topLevelProgram pub get` to create `$lockFilePath`.''');
// We have to download files also with --dry-run to ensure we know the
// archive hashes for downloaded files.
final newLockFile = await result.downloadCachedPackages(cache);

final report = SolveReport(
type,
workspaceRoot.presentationDir,
Expand All @@ -582,7 +581,7 @@ Try running `$topLevelProgram pub get` to create `$lockFilePath`.''');
);

await report.show(summary: true);
if (enforceLockfile && !_lockfilesMatch(lockFile, newLockFile)) {
if (enforceLockfile && !lockFile.samePackageIds(newLockFile)) {
dataError('''
Unable to satisfy `${workspaceRoot.pubspecPath}` using `$lockFilePath`$suffix.
Expand Down Expand Up @@ -1333,25 +1332,6 @@ See https://dart.dev/go/sdk-constraint
bool get _summaryOnlyEnvironment =>
(Platform.environment['PUB_SUMMARY_ONLY'] ?? '0') != '0';

/// Returns true if the packages in [newLockFile] and [previousLockFile] are
/// all the same, meaning:
/// * same set of package-names
/// * for each package
/// * same version number
/// * same resolved description (same content-hash, git hash, path)
bool _lockfilesMatch(LockFile previousLockFile, LockFile newLockFile) {
if (previousLockFile.packages.length != newLockFile.packages.length) {
return false;
}
for (final package in newLockFile.packages.values) {
final oldPackage = previousLockFile.packages[package.name];
if (oldPackage == null) return false; // Package added to resolution.
if (oldPackage.version != package.version) return false;
if (oldPackage.description != package.description) return false;
}
return true;
}

/// Remove any `pubspec.lock` or `.dart_tool/package_config.json` files in
/// workspace packages that are not the root package.
///
Expand Down
36 changes: 36 additions & 0 deletions lib/src/global_packages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ class GlobalPackages {
'pub global activate',
dependencies: [dep],
sources: cache.sources,
sdkConstraints: {
'dart': SdkConstraint.interpretDartSdkConstraint(
VersionConstraint.parse('>=2.12.0'),
defaultUpperBoundConstraint: null,
),
},
),
dir,
[],
Expand Down Expand Up @@ -447,6 +453,36 @@ try:
args,
enableAsserts: enableAsserts,
recompile: (exectuable) async {
final root = entrypoint.workspaceRoot;
final name = exectuable.package;
// Resolve it and download its dependencies.
SolveResult result;
try {
result = await log.spinner(
'Resolving dependencies',
() => resolveVersions(SolveType.get, cache, root),
);
} on SolveFailure catch (e) {
log.error(e.message);
fail('''The package `$name` as currently activated cannot resolve.
Try reactivating the package.
`$topLevelProgram pub global activate $name`
''');
}
// We want the entrypoint to be rooted at 'dep' not the dummy-package.
result.packages.removeWhere((id) => id.name == 'pub global activate');

final newLockFile = await result.downloadCachedPackages(cache);
final sameVersions = entrypoint.lockFile.samePackageIds(newLockFile);
if (!sameVersions) {
dataError('''
The package `$name` as currently activated cannot resolve to the same packages.
Try reactivating the package.
`$topLevelProgram pub global activate $name`
''');
}
await recompile(exectuable);
_refreshBinStubs(entrypoint, executable);
},
Expand Down
18 changes: 12 additions & 6 deletions lib/src/lock_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -415,15 +415,21 @@ ${yamlToString(data)}
return _transitive;
}

/// `true` if [other] has the same packages as `this` in the same versions
/// from the same sources.
/// Returns true if the packages in `this` and [other] are
/// all the same, meaning:
/// * same set of package-names
/// * for each package
/// * same version number
/// * same resolved description (same content-hash, git hash, path)
bool samePackageIds(LockFile other) {
if (packages.length != other.packages.length) {
if (other.packages.length != packages.length) {
return false;
}
for (final id in packages.values) {
final otherId = other.packages[id.name];
if (id != otherId) return false;
for (final package in packages.values) {
final oldPackage = other.packages[package.name];
if (oldPackage == null) return false; // Package added to resolution.
if (oldPackage.version != package.version) return false;
if (oldPackage.description != package.description) return false;
}
return true;
}
Expand Down
8 changes: 4 additions & 4 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: f6dbf021f4b214d85c79822912c5fcd142a2c4869f01222ad371bc51f9f1c356
sha256: c57b02f47e021c9d7ced6d2e28824b315e0fd585578274bc4c2a5db0626f154a
url: "https://pub.dev"
source: hosted
version: "74.0.0"
version: "75.0.0"
_macros:
dependency: transitive
description: dart
Expand All @@ -18,10 +18,10 @@ packages:
dependency: "direct main"
description:
name: analyzer
sha256: f7e8caf82f2d3190881d81012606effdf8a38e6c1ab9e30947149733065f817c
sha256: ef226c581b7cd875f734125b1b9928df3db08cc85ff87ce7d9be89a677aaee23
url: "https://pub.dev"
source: hosted
version: "6.9.0"
version: "6.10.0"
args:
dependency: "direct main"
description:
Expand Down
89 changes: 89 additions & 0 deletions test/global/run/recompiles_if_snapshot_is_out_of_date_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:path/path.dart' as p;
import 'package:pub/src/exit_codes.dart';
import 'package:pub/src/io.dart';
import 'package:pub/src/sdk/sdk_package_config.dart';
import 'package:test/test.dart';

import '../../descriptor.dart' as d;
Expand Down Expand Up @@ -59,4 +61,91 @@ void main() {
]),
]).validate();
});

test('validate resolution before recompilation', () async {
final server = await servePackages();
server.serve(
'foo',
'1.0.0',
deps: {
'bar': {'sdk': 'dart', 'version': '^1.0.0'},
},
contents: [
d.dir('bin', [
d.file('foo.dart', 'main() => print("foo");'),
]),
],
);

await d.dir('dart', [
d.dir('packages', [
d.dir('bar', [
d.libPubspec('bar', '1.0.0', deps: {}),
]),
]),
d.sdkPackagesConfig(
SdkPackageConfig(
'dart',
{'bar': SdkPackage('bar', 'packages/bar')},
1,
),
),
]).create();

await runPub(
args: ['global', 'activate', 'foo'],
environment: {'DART_ROOT': p.join(d.sandbox, 'dart')},
);

await runPub(
args: ['global', 'run', 'foo'],
environment: {'DART_ROOT': p.join(d.sandbox, 'dart')},
output: 'foo',
);

await d.dir('dart', [
d.dir('packages', [
d.dir('bar', [
// Within constraint, but doesn't satisfy pubspec.lock.
d.libPubspec('bar', '1.2.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 package `foo` as currently activated cannot resolve to '
'the same packages'),
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('Because every version of foo depends on bar ^1.0.0 from sdk'),
contains('The package `foo` as currently activated cannot resolve.'),
contains('Try reactivating the package'),
),
exitCode: 1,
);
});
}

0 comments on commit 42017cf

Please sign in to comment.