Skip to content

Commit 3df3ba5

Browse files
[tool] Add initial update-dependency command (flutter#3632)
[tool] Add initial `update-dependency` command
1 parent 1cac25d commit 3df3ba5

File tree

4 files changed

+572
-0
lines changed

4 files changed

+572
-0
lines changed

script/tool/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,26 @@ the branch point from `upstream/main`. For more complex use cases where you want
135135
a different diff point, you can pass a different `--base-branch`, or use
136136
`--base-sha` to pick the exact diff point.
137137

138+
### Update a dependency
139+
140+
`update-dependency` will update a pub dependency to a new version.
141+
142+
For instance, to updated to version 3.0.0 of `some_package` in every package
143+
that depends on it:
144+
145+
```sh
146+
cd <repository root>
147+
dart run script/tool/bin/flutter_plugin_tools.dart update-dependency \
148+
--pub-package=some_package \
149+
--version=3.0.0 \
150+
```
151+
152+
If a `--version` is not provided, the latest version from pub will be used.
153+
154+
Currently this only updates the dependency itself in pubspec.yaml, but in the
155+
future this will also update any generated code for packages that use code
156+
generation (e.g., regenerating mocks when updating `mockito`).
157+
138158
### Publish a Release
139159

140160
**Releases are automated for `flutter/packages`.**

script/tool/lib/src/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import 'pubspec_check_command.dart';
3131
import 'readme_check_command.dart';
3232
import 'remove_dev_dependencies_command.dart';
3333
import 'test_command.dart';
34+
import 'update_dependency_command.dart';
3435
import 'update_excerpts_command.dart';
3536
import 'update_min_sdk_command.dart';
3637
import 'update_release_info_command.dart';
@@ -77,6 +78,7 @@ void main(List<String> args) {
7778
..addCommand(ReadmeCheckCommand(packagesDir))
7879
..addCommand(RemoveDevDependenciesCommand(packagesDir))
7980
..addCommand(TestCommand(packagesDir))
81+
..addCommand(UpdateDependencyCommand(packagesDir))
8082
..addCommand(UpdateExcerptsCommand(packagesDir))
8183
..addCommand(UpdateMinSdkCommand(packagesDir))
8284
..addCommand(UpdateReleaseInfoCommand(packagesDir))
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:file/file.dart';
6+
import 'package:http/http.dart' as http;
7+
import 'package:pub_semver/pub_semver.dart';
8+
import 'package:pubspec_parse/pubspec_parse.dart';
9+
import 'package:yaml_edit/yaml_edit.dart';
10+
11+
import 'common/core.dart';
12+
import 'common/package_looping_command.dart';
13+
import 'common/pub_version_finder.dart';
14+
import 'common/repository_package.dart';
15+
16+
const int _exitIncorrectTargetDependency = 3;
17+
const int _exitNoTargetVersion = 4;
18+
19+
/// A command to update a dependency in packages.
20+
///
21+
/// This is intended to expand over time to support any sort of dependency that
22+
/// packages use, including pub packages and native dependencies, and should
23+
/// include any tasks related to the dependency (e.g., regenerating files when
24+
/// updating a dependency that is responsible for code generation).
25+
class UpdateDependencyCommand extends PackageLoopingCommand {
26+
/// Creates an instance of the version check command.
27+
UpdateDependencyCommand(
28+
Directory packagesDir, {
29+
http.Client? httpClient,
30+
}) : _pubVersionFinder =
31+
PubVersionFinder(httpClient: httpClient ?? http.Client()),
32+
super(packagesDir) {
33+
argParser.addOption(
34+
_pubPackageFlag,
35+
help: 'A pub package to update.',
36+
);
37+
argParser.addOption(
38+
_versionFlag,
39+
help: 'The version to update to.\n\n'
40+
'- For pub, defaults to the latest published version if not '
41+
'provided. This can be any constraint that pubspec.yaml allows; a '
42+
'specific version will be treated as the exact version for '
43+
'dependencies that are alread pinned, or a ^ range for those that '
44+
'are unpinned.',
45+
);
46+
}
47+
48+
static const String _pubPackageFlag = 'pub-package';
49+
static const String _versionFlag = 'version';
50+
51+
final PubVersionFinder _pubVersionFinder;
52+
53+
late final String? _targetPubPackage;
54+
late final String _targetVersion;
55+
56+
@override
57+
final String name = 'update-dependency';
58+
59+
@override
60+
final String description = 'Updates a dependency in a package.';
61+
62+
@override
63+
bool get hasLongOutput => false;
64+
65+
@override
66+
PackageLoopingType get packageLoopingType =>
67+
PackageLoopingType.includeAllSubpackages;
68+
69+
@override
70+
Future<void> initializeRun() async {
71+
const Set<String> targetFlags = <String>{_pubPackageFlag};
72+
final Set<String> passedTargetFlags =
73+
targetFlags.where((String flag) => argResults![flag] != null).toSet();
74+
if (passedTargetFlags.length != 1) {
75+
printError(
76+
'Exactly one of the target flags must be provided: (${targetFlags.join(', ')})');
77+
throw ToolExit(_exitIncorrectTargetDependency);
78+
}
79+
_targetPubPackage = getNullableStringArg(_pubPackageFlag);
80+
if (_targetPubPackage != null) {
81+
final String? version = getNullableStringArg(_versionFlag);
82+
if (version == null) {
83+
final PubVersionFinderResponse response = await _pubVersionFinder
84+
.getPackageVersion(packageName: _targetPubPackage!);
85+
switch (response.result) {
86+
case PubVersionFinderResult.success:
87+
_targetVersion = response.versions.first.toString();
88+
break;
89+
case PubVersionFinderResult.fail:
90+
printError('''
91+
Error fetching $_targetPubPackage version from pub: ${response.httpResponse.statusCode}:
92+
${response.httpResponse.body}
93+
''');
94+
throw ToolExit(_exitNoTargetVersion);
95+
case PubVersionFinderResult.noPackageFound:
96+
printError('$_targetPubPackage does not exist on pub');
97+
throw ToolExit(_exitNoTargetVersion);
98+
}
99+
} else {
100+
_targetVersion = version;
101+
}
102+
}
103+
}
104+
105+
@override
106+
Future<void> completeRun() async {
107+
_pubVersionFinder.httpClient.close();
108+
}
109+
110+
@override
111+
Future<PackageResult> runForPackage(RepositoryPackage package) async {
112+
if (_targetPubPackage != null) {
113+
return _runForPubDependency(package, _targetPubPackage!);
114+
}
115+
// TODO(stuartmorgan): Add othe dependency types here (e.g., maven).
116+
117+
return PackageResult.fail();
118+
}
119+
120+
/// Handles all of the updates for [package] when the target dependency is
121+
/// a pub dependency.
122+
Future<PackageResult> _runForPubDependency(
123+
RepositoryPackage package, String dependency) async {
124+
final _PubDependencyInfo? dependencyInfo =
125+
_getPubDependencyInfo(package, dependency);
126+
if (dependencyInfo == null) {
127+
return PackageResult.skip('Does not depend on $dependency');
128+
} else if (!dependencyInfo.hosted) {
129+
return PackageResult.skip('$dependency in not a hosted dependency');
130+
}
131+
132+
final String sectionKey = dependencyInfo.type == _PubDependencyType.dev
133+
? 'dev_dependencies'
134+
: 'dependencies';
135+
final String versionString;
136+
final VersionConstraint parsedConstraint =
137+
VersionConstraint.parse(_targetVersion);
138+
// If the provided string was a constraint, or if it's a specific
139+
// version but the package has a pinned dependency, use it as-is.
140+
if (dependencyInfo.pinned ||
141+
parsedConstraint is! VersionRange ||
142+
parsedConstraint.min != parsedConstraint.max) {
143+
versionString = _targetVersion;
144+
} else {
145+
// Otherwise, it's a specific version; treat it as '^version'.
146+
final Version minVersion = parsedConstraint.min!;
147+
versionString = '^$minVersion';
148+
}
149+
150+
print('${indentation}Updating to "$versionString"');
151+
if (versionString == dependencyInfo.constraintString) {
152+
return PackageResult.skip('Already depends on $versionString');
153+
}
154+
final YamlEditor editablePubspec =
155+
YamlEditor(package.pubspecFile.readAsStringSync());
156+
editablePubspec.update(
157+
<String>[sectionKey, dependency],
158+
versionString,
159+
);
160+
package.pubspecFile.writeAsStringSync(editablePubspec.toString());
161+
162+
// TODO(stuartmorgan): Add additionally handling of known packages that
163+
// do file generation (mockito, pigeon, etc.).
164+
165+
return PackageResult.success();
166+
}
167+
168+
/// Returns information about the current dependency of [package] on
169+
/// the package named [dependencyName], or null if there is no dependency.
170+
_PubDependencyInfo? _getPubDependencyInfo(
171+
RepositoryPackage package, String dependencyName) {
172+
final Pubspec pubspec = package.parsePubspec();
173+
174+
Dependency? dependency;
175+
final _PubDependencyType type;
176+
if (pubspec.dependencies.containsKey(dependencyName)) {
177+
dependency = pubspec.dependencies[dependencyName];
178+
type = _PubDependencyType.normal;
179+
} else if (pubspec.devDependencies.containsKey(dependencyName)) {
180+
dependency = pubspec.devDependencies[dependencyName];
181+
type = _PubDependencyType.dev;
182+
} else {
183+
return null;
184+
}
185+
if (dependency != null && dependency is HostedDependency) {
186+
final VersionConstraint version = dependency.version;
187+
return _PubDependencyInfo(
188+
type,
189+
pinned: version is VersionRange && version.min == version.max,
190+
hosted: true,
191+
constraintString: version.toString(),
192+
);
193+
}
194+
return _PubDependencyInfo(type, pinned: false, hosted: false);
195+
}
196+
}
197+
198+
class _PubDependencyInfo {
199+
const _PubDependencyInfo(this.type,
200+
{required this.pinned, required this.hosted, this.constraintString});
201+
final _PubDependencyType type;
202+
final bool pinned;
203+
final bool hosted;
204+
final String? constraintString;
205+
}
206+
207+
enum _PubDependencyType { normal, dev }

0 commit comments

Comments
 (0)