Skip to content

Commit c383ab9

Browse files
dnys1Dillon Nys
authored andcommitted
chore(aft): Better handling of pre-release Dart SDK usage (#3569)
Some packages in the repo (for example, the `actions` pkg) set a pre-release Dart SDK constraint so that preview features of Dart can be leveraged. This updates `aft` so that: 1) `bootstrap` skips packages which set this constraint if the current Dart SDK doesn't support it; and 2) Ignores preview constraints in the constraints checker, since these packages do not need to conform to the global setting.
1 parent 7fc07cf commit c383ab9

File tree

7 files changed

+261
-68
lines changed

7 files changed

+261
-68
lines changed

packages/aft/lib/src/commands/amplify_command.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ abstract class AmplifyCommand extends Command<void>
190190
if (globalResults?['verbose'] as bool? ?? false) {
191191
AWSLogger().logLevel = LogLevel.verbose;
192192
}
193-
logger.verbose('Got configuration: $aftConfig');
193+
logger
194+
..info('Found Dart SDK: $activeDartSdkVersion')
195+
..verbose('Got configuration: $aftConfig');
194196
repo = await Repo.open(aftConfig, logger: logger);
195197
}
196198

packages/aft/lib/src/commands/bootstrap_command.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ const amplifyEnvironments = <String, String>{};
7979
// command is significantly newer/older than the embedded one.
8080
(pkg) => pkg.name != 'aft',
8181
)
82+
.where(
83+
// Skip bootstrapping packages which set incompatible Dart SDK constraints,
84+
// e.g. packages which are leveraging preview features.
85+
//
86+
// The problem of packages setting incorrect constraints, for example setting
87+
// `^3.0.5` when the current repo constraint is `^3.0.0` and we're running
88+
// `aft` with `3.0.1` is a different issue handled by the constraints commands.
89+
(pkg) {
90+
final compatibleWithActiveSdk = pkg.compatibleWithActiveSdk;
91+
if (!compatibleWithActiveSdk) {
92+
logger.info(
93+
'Skipping package ${pkg.name} since it sets an incompatible Dart SDK constraint: '
94+
'${pkg.dartSdkConstraint}',
95+
);
96+
}
97+
return compatibleWithActiveSdk;
98+
},
99+
)
82100
.expand((pkg) => [pkg, pkg.example, pkg.docs])
83101
.nonNulls;
84102
for (final package in bootstrapPackages) {

packages/aft/lib/src/config/config.dart

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,6 @@ class PackageInfo
317317
pubspecInfo.pubspec.version ??
318318
(throw StateError('No version for package: $name'));
319319

320-
@override
321-
String get runtimeTypeName => 'PackageInfo';
322-
323320
/// Skip package checks for Flutter packages when running in CI without
324321
/// Flutter, which may happen when testing Dart-only packages or specific
325322
/// Dart versions.
@@ -330,9 +327,21 @@ class PackageInfo
330327
flavor == PackageFlavor.flutter;
331328
}
332329

330+
/// Whether this package is compatible with the active Dart SDK
331+
/// in the current environment.
332+
bool get compatibleWithActiveSdk =>
333+
dartSdkConstraint.allows(activeDartSdkVersion);
334+
335+
/// The Dart SDK constraint set by the package.
336+
VersionConstraint get dartSdkConstraint =>
337+
pubspecInfo.pubspec.environment!['sdk']!;
338+
333339
@override
334340
List<Object?> get props => [name];
335341

342+
@override
343+
String get runtimeTypeName => 'PackageInfo';
344+
336345
@override
337346
int compareTo(PackageInfo other) {
338347
return path.compareTo(other.path);
@@ -403,3 +412,30 @@ extension DirectoryX on Directory {
403412
}
404413
}
405414
}
415+
416+
/// The version of the current environment's Dart SDK.
417+
///
418+
/// This parses the version from calling `dart --version`.
419+
final Version activeDartSdkVersion = () {
420+
final ProcessResult(
421+
:exitCode,
422+
:stdout,
423+
:stderr,
424+
) = Process.runSync('dart', ['--version']);
425+
if (exitCode != 0) {
426+
throw Exception(
427+
'Error running `dart --version` ($exitCode): $stderr',
428+
);
429+
}
430+
// Example output:
431+
// Dart SDK version: 3.1.0 (stable) (Tue Aug 15 21:33:36 2023 +0000) on "macos_arm64"
432+
final activeSdkVersionRaw = _versionRegex.firstMatch(stdout as String);
433+
if (activeSdkVersionRaw == null) {
434+
throw Exception(
435+
'Running `dart --version` produced unexpected output: $stdout',
436+
);
437+
}
438+
return Version.parse(activeSdkVersionRaw.group(0)!);
439+
}();
440+
441+
final _versionRegex = RegExp(r'\d+\.\d+\.\d+(-[a-zA-Z\d]+)?');

packages/aft/lib/src/constraints_checker.dart

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,15 @@ sealed class ConstraintsChecker {
4848
message: errorMessage,
4949
),
5050
);
51+
return false;
5152
case ConstraintsAction.apply:
5253
case ConstraintsAction.update:
5354
package.pubspecInfo.pubspecYamlEditor.update(
5455
dependencyPath,
5556
expectedConstraint.toString(),
5657
);
58+
return true;
5759
}
58-
return false;
5960
}
6061
}
6162

@@ -111,7 +112,16 @@ final class GlobalConstraintChecker extends ConstraintsChecker {
111112
// Check Dart SDK contraint
112113
final globalSdkConstraint = globalEnvironment.sdk;
113114
final localSdkConstraint = environment['sdk'];
114-
final satisfiesSdkConstraint = globalSdkConstraint == localSdkConstraint;
115+
final satisfiesSdkConstraint = switch (localSdkConstraint) {
116+
// In the special case where we've set a constraint like `^3.2.0-0`
117+
// (i.e. a pre-release constraint), the package is allowed to differ
118+
// from all other packages iff it's not a publishable package.
119+
VersionRange(:final min?) when min.isPreRelease => !package.isPublishable,
120+
121+
// Otherwise, in all other cases, the package's listed SDK constraint
122+
// must exactly match all other packages.
123+
_ => globalSdkConstraint == localSdkConstraint,
124+
};
115125
if (!satisfiesSdkConstraint) {
116126
_processConstraint(
117127
package: package,
@@ -140,7 +150,11 @@ final class GlobalConstraintChecker extends ConstraintsChecker {
140150
}
141151
}
142152

143-
return satisfiesSdkConstraint && satisfiesFlutterConstraint;
153+
return switch (action) {
154+
ConstraintsAction.check =>
155+
satisfiesSdkConstraint && satisfiesFlutterConstraint,
156+
_ => true,
157+
};
144158
}
145159

146160
/// Runs [action] for each dependency in [package].

packages/aft/test/common.dart

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'package:aft/aft.dart';
5+
import 'package:pub_semver/pub_semver.dart';
6+
import 'package:pubspec_parse/pubspec_parse.dart';
7+
import 'package:yaml/yaml.dart';
8+
import 'package:yaml_edit/yaml_edit.dart';
9+
10+
/// Creates a dummy package for testing repo operations.
11+
MapEntry<PackageInfo, List<PackageInfo>> dummyPackage(
12+
String name, {
13+
Version? version,
14+
bool publishable = true,
15+
VersionConstraint? sdkConstraint,
16+
Map<PackageInfo, VersionConstraint> deps = const {},
17+
Map<PackageInfo, VersionConstraint> devDeps = const {},
18+
}) {
19+
final path = 'packages/$name';
20+
sdkConstraint ??= VersionConstraint.compatibleWith(Version(3, 0, 0));
21+
22+
final pubspecEditor = YamlEditor('''
23+
name: $name
24+
25+
environment:
26+
sdk: $sdkConstraint
27+
28+
dependencies: {}
29+
30+
dev_dependencies: {}
31+
''');
32+
33+
if (version != null) {
34+
pubspecEditor.update(['version'], version.toString());
35+
}
36+
37+
void addConstraints(
38+
Map<PackageInfo, VersionConstraint> constraints,
39+
DependencyType type,
40+
) {
41+
for (final MapEntry(key: dep, value: constraint) in constraints.entries) {
42+
final path = <String>[type.key, dep.name];
43+
pubspecEditor.update(path, constraint.toString());
44+
}
45+
}
46+
47+
addConstraints(deps, DependencyType.dependency);
48+
addConstraints(devDeps, DependencyType.devDependency);
49+
50+
if (!publishable) {
51+
pubspecEditor.update(['publish_to'], 'none');
52+
}
53+
54+
final pubspecYaml = pubspecEditor.toString();
55+
final pubspec = Pubspec.parse(pubspecYaml);
56+
final pubspecMap = loadYamlNode(pubspecYaml) as YamlMap;
57+
58+
final package = PackageInfo(
59+
name: name,
60+
path: path,
61+
pubspecInfo: PubspecInfo(
62+
pubspec: pubspec,
63+
pubspecYaml: pubspecYaml,
64+
pubspecMap: pubspecMap,
65+
uri: Uri.base.resolve(path),
66+
),
67+
flavor: PackageFlavor.dart,
68+
);
69+
return MapEntry(package, [...deps.keys, ...devDeps.keys]);
70+
}

packages/aft/test/constraints_checker_test.dart

Lines changed: 87 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,97 @@
44
import 'package:aft/aft.dart';
55
import 'package:aft/src/constraints_checker.dart';
66
import 'package:pub_semver/pub_semver.dart';
7-
import 'package:pubspec_parse/pubspec_parse.dart';
87
import 'package:test/test.dart';
9-
import 'package:yaml/yaml.dart';
108
import 'package:yaml_edit/yaml_edit.dart';
119

10+
import 'common.dart';
11+
1212
void main() {
13+
group('GlobalConstraintsChecker', () {
14+
for (final action in ConstraintsAction.values) {
15+
test(
16+
'handles SDK constraints for preview Dart versions (${action.name})',
17+
() {
18+
final preReleaseConstraint = VersionConstraint.compatibleWith(
19+
Version(3, 2, 0, pre: '0'),
20+
);
21+
final actions = dummyPackage(
22+
'actions',
23+
publishable: false,
24+
sdkConstraint: preReleaseConstraint,
25+
);
26+
final amplifyFlutter = dummyPackage(
27+
'amplify_flutter',
28+
sdkConstraint: preReleaseConstraint,
29+
);
30+
final checker = GlobalConstraintChecker(
31+
action,
32+
const {},
33+
Environment(
34+
sdk: VersionConstraint.compatibleWith(Version(3, 0, 0)),
35+
),
36+
);
37+
38+
{
39+
expect(
40+
checker.checkConstraints(actions.key),
41+
isTrue,
42+
reason:
43+
'Package is not publishable and can take a prerelease constraint '
44+
'to leverage new Dart features',
45+
);
46+
expect(checker.mismatchedDependencies, isEmpty);
47+
expect(actions.key.pubspecInfo.pubspecYamlEditor.edits, isEmpty);
48+
}
49+
50+
{
51+
switch (action) {
52+
case ConstraintsAction.apply || ConstraintsAction.update:
53+
expect(
54+
checker.checkConstraints(amplifyFlutter.key),
55+
isTrue,
56+
);
57+
expect(
58+
amplifyFlutter.key.pubspecInfo.pubspecYamlEditor.edits.single,
59+
isA<SourceEdit>().having(
60+
(edit) => edit.replacement.trim(),
61+
'replacement',
62+
'^3.0.0',
63+
),
64+
);
65+
expect(checker.mismatchedDependencies, isEmpty);
66+
case ConstraintsAction.check:
67+
expect(
68+
checker.checkConstraints(amplifyFlutter.key),
69+
isFalse,
70+
reason:
71+
'Package is publishable and must match the global SDK constraint',
72+
);
73+
expect(
74+
checker.mismatchedDependencies.single,
75+
isA<MismatchedDependency>()
76+
.having(
77+
(err) => err.package.name,
78+
'packageName',
79+
'amplify_flutter',
80+
)
81+
.having(
82+
(err) => err.dependencyName,
83+
'dependencyName',
84+
'sdk',
85+
),
86+
);
87+
expect(
88+
amplifyFlutter.key.pubspecInfo.pubspecYamlEditor.edits,
89+
isEmpty,
90+
);
91+
}
92+
}
93+
},
94+
);
95+
}
96+
});
97+
1398
group('PublishConstraintsChecker', () {
1499
for (final action in ConstraintsAction.values) {
15100
final result = switch (action) {
@@ -119,62 +204,3 @@ void main() {
119204
}
120205
});
121206
}
122-
123-
MapEntry<PackageInfo, List<PackageInfo>> dummyPackage(
124-
String name, {
125-
Version? version,
126-
bool publishable = true,
127-
Map<PackageInfo, VersionConstraint> deps = const {},
128-
Map<PackageInfo, VersionConstraint> devDeps = const {},
129-
}) {
130-
final path = 'packages/$name';
131-
132-
final pubspecEditor = YamlEditor('''
133-
name: $name
134-
135-
environment:
136-
sdk: ^3.0.0
137-
138-
dependencies: {}
139-
140-
dev_dependencies: {}
141-
''');
142-
143-
if (version != null) {
144-
pubspecEditor.update(['version'], version.toString());
145-
}
146-
147-
void addConstraints(
148-
Map<PackageInfo, VersionConstraint> constraints,
149-
DependencyType type,
150-
) {
151-
for (final MapEntry(key: dep, value: constraint) in constraints.entries) {
152-
final path = <String>[type.key, dep.name];
153-
pubspecEditor.update(path, constraint.toString());
154-
}
155-
}
156-
157-
addConstraints(deps, DependencyType.dependency);
158-
addConstraints(devDeps, DependencyType.devDependency);
159-
160-
if (!publishable) {
161-
pubspecEditor.update(['publish_to'], 'none');
162-
}
163-
164-
final pubspecYaml = pubspecEditor.toString();
165-
final pubspec = Pubspec.parse(pubspecYaml);
166-
final pubspecMap = loadYamlNode(pubspecYaml) as YamlMap;
167-
168-
final package = PackageInfo(
169-
name: name,
170-
path: path,
171-
pubspecInfo: PubspecInfo(
172-
pubspec: pubspec,
173-
pubspecYaml: pubspecYaml,
174-
pubspecMap: pubspecMap,
175-
uri: Uri.base.resolve(path),
176-
),
177-
flavor: PackageFlavor.dart,
178-
);
179-
return MapEntry(package, [...deps.keys, ...devDeps.keys]);
180-
}

0 commit comments

Comments
 (0)