Make flutter update-packages
run in parallel (#91006)
This modifies the flutter update-packages and flutter update-packages --force-upgrade commands so that the many invocations of "dart pub get" in each repo project run in parallel instead of in series.
This commit is contained in:
parent
fc02dcbb4c
commit
01af8e5987
98
packages/flutter_tools/lib/src/base/task_queue.dart
Normal file
98
packages/flutter_tools/lib/src/base/task_queue.dart
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import '../globals_null_migrated.dart' as globals;
|
||||||
|
|
||||||
|
/// A closure type used by the [TaskQueue].
|
||||||
|
typedef TaskQueueClosure<T> = Future<T> Function();
|
||||||
|
|
||||||
|
/// A task queue of Futures to be completed in parallel, throttling
|
||||||
|
/// the number of simultaneous tasks.
|
||||||
|
///
|
||||||
|
/// The tasks return results of type T.
|
||||||
|
class TaskQueue<T> {
|
||||||
|
/// Creates a task queue with a maximum number of simultaneous jobs.
|
||||||
|
/// The [maxJobs] parameter defaults to the number of CPU cores on the
|
||||||
|
/// system.
|
||||||
|
TaskQueue({int? maxJobs})
|
||||||
|
: maxJobs = maxJobs ?? globals.platform.numberOfProcessors;
|
||||||
|
|
||||||
|
/// The maximum number of jobs that this queue will run simultaneously.
|
||||||
|
final int maxJobs;
|
||||||
|
|
||||||
|
final Queue<_TaskQueueItem<T>> _pendingTasks = Queue<_TaskQueueItem<T>>();
|
||||||
|
final Set<_TaskQueueItem<T>> _activeTasks = <_TaskQueueItem<T>>{};
|
||||||
|
final Set<Completer<void>> _completeListeners = <Completer<void>>{};
|
||||||
|
|
||||||
|
/// Returns a future that completes when all tasks in the [TaskQueue] are
|
||||||
|
/// complete.
|
||||||
|
Future<void> get tasksComplete {
|
||||||
|
// In case this is called when there are no tasks, we want it to
|
||||||
|
// signal complete immediately.
|
||||||
|
if (_activeTasks.isEmpty && _pendingTasks.isEmpty) {
|
||||||
|
return Future<void>.value();
|
||||||
|
}
|
||||||
|
final Completer<void> completer = Completer<void>();
|
||||||
|
_completeListeners.add(completer);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a single closure to the task queue, returning a future that
|
||||||
|
/// completes when the task completes.
|
||||||
|
Future<T> add(TaskQueueClosure<T> task) {
|
||||||
|
final Completer<T> completer = Completer<T>();
|
||||||
|
_pendingTasks.add(_TaskQueueItem<T>(task, completer));
|
||||||
|
if (_activeTasks.length < maxJobs) {
|
||||||
|
_processTask();
|
||||||
|
}
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a single task.
|
||||||
|
void _processTask() {
|
||||||
|
if (_pendingTasks.isNotEmpty && _activeTasks.length <= maxJobs) {
|
||||||
|
final _TaskQueueItem<T> item = _pendingTasks.removeFirst();
|
||||||
|
_activeTasks.add(item);
|
||||||
|
item.onComplete = () {
|
||||||
|
_activeTasks.remove(item);
|
||||||
|
_processTask();
|
||||||
|
};
|
||||||
|
item.run();
|
||||||
|
} else {
|
||||||
|
_checkForCompletion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkForCompletion() {
|
||||||
|
if (_activeTasks.isEmpty && _pendingTasks.isEmpty) {
|
||||||
|
for (final Completer<void> completer in _completeListeners) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_completeListeners.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TaskQueueItem<T> {
|
||||||
|
_TaskQueueItem(this._closure, this._completer, {this.onComplete});
|
||||||
|
|
||||||
|
final TaskQueueClosure<T> _closure;
|
||||||
|
final Completer<T> _completer;
|
||||||
|
void Function()? onComplete;
|
||||||
|
|
||||||
|
Future<void> run() async {
|
||||||
|
try {
|
||||||
|
_completer.complete(await _closure());
|
||||||
|
} catch (e) { // ignore: avoid_catches_without_on_clauses
|
||||||
|
_completer.completeError(e);
|
||||||
|
} finally {
|
||||||
|
onComplete?.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ import '../base/context.dart';
|
|||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/logger.dart';
|
import '../base/logger.dart';
|
||||||
import '../base/net.dart';
|
import '../base/net.dart';
|
||||||
|
import '../base/task_queue.dart';
|
||||||
import '../cache.dart';
|
import '../cache.dart';
|
||||||
import '../dart/pub.dart';
|
import '../dart/pub.dart';
|
||||||
import '../globals_null_migrated.dart' as globals;
|
import '../globals_null_migrated.dart' as globals;
|
||||||
@ -160,9 +161,6 @@ class UpdatePackagesCommand extends FlutterCommand {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Future<void> _downloadCoverageData() async {
|
Future<void> _downloadCoverageData() async {
|
||||||
final Status status = globals.logger.startProgress(
|
|
||||||
'Downloading lcov data for package:flutter...',
|
|
||||||
);
|
|
||||||
final String urlBase = globals.platform.environment['FLUTTER_STORAGE_BASE_URL'] ?? 'https://storage.googleapis.com';
|
final String urlBase = globals.platform.environment['FLUTTER_STORAGE_BASE_URL'] ?? 'https://storage.googleapis.com';
|
||||||
final Uri coverageUri = Uri.parse('$urlBase/flutter_infra_release/flutter/coverage/lcov.info');
|
final Uri coverageUri = Uri.parse('$urlBase/flutter_infra_release/flutter/coverage/lcov.info');
|
||||||
final List<int> data = await _net.fetchUrl(coverageUri);
|
final List<int> data = await _net.fetchUrl(coverageUri);
|
||||||
@ -176,7 +174,6 @@ class UpdatePackagesCommand extends FlutterCommand {
|
|||||||
globals.fs.file(globals.fs.path.join(coverageDir, 'lcov.info'))
|
globals.fs.file(globals.fs.path.join(coverageDir, 'lcov.info'))
|
||||||
..createSync(recursive: true)
|
..createSync(recursive: true)
|
||||||
..writeAsBytesSync(data, flush: true);
|
..writeAsBytesSync(data, flush: true);
|
||||||
status.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -271,104 +268,120 @@ class UpdatePackagesCommand extends FlutterCommand {
|
|||||||
return FlutterCommandResult.success();
|
return FlutterCommandResult.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (upgrade || isPrintPaths || isPrintTransitiveClosure) {
|
final Map<String, PubspecDependency> dependencies = <String, PubspecDependency>{};
|
||||||
globals.printStatus('Upgrading packages...');
|
final bool doUpgrade = upgrade || isPrintPaths || isPrintTransitiveClosure;
|
||||||
|
if (doUpgrade) {
|
||||||
// This feature attempts to collect all the packages used across all the
|
// This feature attempts to collect all the packages used across all the
|
||||||
// pubspec.yamls in the repo (including via transitive dependencies), and
|
// pubspec.yamls in the repo (including via transitive dependencies), and
|
||||||
// find the latest version of each that can be used while keeping each
|
// find the latest version of each that can be used while keeping each
|
||||||
// such package fixed at a single version across all the pubspec.yamls.
|
// such package fixed at a single version across all the pubspec.yamls.
|
||||||
//
|
globals.printStatus('Upgrading packages...');
|
||||||
// First, collect up the explicit dependencies:
|
}
|
||||||
final List<PubspecYaml> pubspecs = <PubspecYaml>[];
|
|
||||||
final Map<String, PubspecDependency> dependencies = <String, PubspecDependency>{};
|
// First, collect up the explicit dependencies:
|
||||||
final Set<String> specialDependencies = <String>{};
|
final List<PubspecYaml> pubspecs = <PubspecYaml>[];
|
||||||
for (final Directory directory in packages) { // these are all the directories with pubspec.yamls we care about
|
final Set<String> specialDependencies = <String>{};
|
||||||
|
// Visit all the directories with pubspec.yamls we care about.
|
||||||
|
for (final Directory directory in packages) {
|
||||||
|
if (doUpgrade) {
|
||||||
globals.printTrace('Reading pubspec.yaml from: ${directory.path}');
|
globals.printTrace('Reading pubspec.yaml from: ${directory.path}');
|
||||||
PubspecYaml pubspec;
|
}
|
||||||
try {
|
PubspecYaml pubspec;
|
||||||
pubspec = PubspecYaml(directory); // this parses the pubspec.yaml
|
try {
|
||||||
} on String catch (message) {
|
pubspec = PubspecYaml(directory); // this parses the pubspec.yaml
|
||||||
throwToolExit(message);
|
} on String catch (message) {
|
||||||
|
throwToolExit(message);
|
||||||
|
}
|
||||||
|
pubspecs.add(pubspec); // remember it for later
|
||||||
|
for (final PubspecDependency dependency in pubspec.allDependencies) { // this is all the explicit dependencies
|
||||||
|
if (dependencies.containsKey(dependency.name)) {
|
||||||
|
// If we've seen the dependency before, make sure that we are
|
||||||
|
// importing it the same way. There's several ways to import a
|
||||||
|
// dependency. Hosted (from pub via version number), by path (e.g.
|
||||||
|
// pointing at the version of a package we get from the Dart SDK
|
||||||
|
// that we download with Flutter), by SDK (e.g. the "flutter"
|
||||||
|
// package is explicitly from "sdk: flutter").
|
||||||
|
//
|
||||||
|
// This makes sure that we don't import a package in two different
|
||||||
|
// ways, e.g. by saying "sdk: flutter" in one pubspec.yaml and
|
||||||
|
// saying "path: ../../..." in another.
|
||||||
|
final PubspecDependency previous = dependencies[dependency.name];
|
||||||
|
if (dependency.kind != previous.kind || dependency.lockTarget != previous.lockTarget) {
|
||||||
|
throwToolExit(
|
||||||
|
'Inconsistent requirements around ${dependency.name}; '
|
||||||
|
'saw ${dependency.kind} (${dependency.lockTarget}) in "${dependency.sourcePath}" '
|
||||||
|
'and ${previous.kind} (${previous.lockTarget}) in "${previous.sourcePath}".'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pubspecs.add(pubspec); // remember it for later
|
// Remember this dependency by name so we can look it up again.
|
||||||
for (final PubspecDependency dependency in pubspec.allDependencies) { // this is all the explicit dependencies
|
dependencies[dependency.name] = dependency;
|
||||||
if (dependencies.containsKey(dependency.name)) {
|
// Normal dependencies are those we get from pub. The others we
|
||||||
// If we've seen the dependency before, make sure that we are
|
// already implicitly pin since we pull down one version of the
|
||||||
// importing it the same way. There's several ways to import a
|
// Flutter and Dart SDKs, so we track which those are here so that we
|
||||||
// dependency. Hosted (from pub via version number), by path (e.g.
|
// can omit them from our list of pinned dependencies later.
|
||||||
// pointing at the version of a package we get from the Dart SDK
|
if (dependency.kind != DependencyKind.normal) {
|
||||||
// that we download with Flutter), by SDK (e.g. the "flutter"
|
specialDependencies.add(dependency.name);
|
||||||
// package is explicitly from "sdk: flutter").
|
|
||||||
//
|
|
||||||
// This makes sure that we don't import a package in two different
|
|
||||||
// ways, e.g. by saying "sdk: flutter" in one pubspec.yaml and
|
|
||||||
// saying "path: ../../..." in another.
|
|
||||||
final PubspecDependency previous = dependencies[dependency.name];
|
|
||||||
if (dependency.kind != previous.kind || dependency.lockTarget != previous.lockTarget) {
|
|
||||||
throwToolExit(
|
|
||||||
'Inconsistent requirements around ${dependency.name}; '
|
|
||||||
'saw ${dependency.kind} (${dependency.lockTarget}) in "${dependency.sourcePath}" '
|
|
||||||
'and ${previous.kind} (${previous.lockTarget}) in "${previous.sourcePath}".'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Remember this dependency by name so we can look it up again.
|
|
||||||
dependencies[dependency.name] = dependency;
|
|
||||||
// Normal dependencies are those we get from pub. The others we
|
|
||||||
// already implicitly pin since we pull down one version of the
|
|
||||||
// Flutter and Dart SDKs, so we track which those are here so that we
|
|
||||||
// can omit them from our list of pinned dependencies later.
|
|
||||||
if (dependency.kind != DependencyKind.normal) {
|
|
||||||
specialDependencies.add(dependency.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we have all the dependencies we explicitly care about, we are
|
// Now that we have all the dependencies we explicitly care about, we are
|
||||||
// going to create a fake package and then run "pub upgrade" on it. The
|
// going to create a fake package and then run either "pub upgrade" or "pub
|
||||||
// pub tool will attempt to bring these dependencies up to the most recent
|
// get" on it, depending on whether we are upgrading or not. If upgrading,
|
||||||
// possible versions while honoring all their constraints.
|
// the pub tool will attempt to bring these dependencies up to the most
|
||||||
final PubDependencyTree tree = PubDependencyTree(); // object to collect results
|
// recent possible versions while honoring all their constraints. If not
|
||||||
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_update_packages.');
|
// upgrading the pub tool will attempt to download any necessary package
|
||||||
try {
|
// versions to the pub cache to warm the cache.
|
||||||
final File fakePackage = _pubspecFor(tempDir);
|
final PubDependencyTree tree = PubDependencyTree(); // object to collect results
|
||||||
fakePackage.createSync();
|
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_update_packages.');
|
||||||
fakePackage.writeAsStringSync(_generateFakePubspec(dependencies.values));
|
try {
|
||||||
// Create a synthetic flutter SDK so that transitive flutter SDK
|
final File fakePackage = _pubspecFor(tempDir);
|
||||||
// constraints are not affected by this upgrade.
|
fakePackage.createSync();
|
||||||
Directory temporaryFlutterSdk;
|
fakePackage.writeAsStringSync(
|
||||||
if (upgrade) {
|
_generateFakePubspec(
|
||||||
temporaryFlutterSdk = createTemporaryFlutterSdk(
|
dependencies.values,
|
||||||
globals.logger,
|
useAnyVersion: doUpgrade,
|
||||||
globals.fs,
|
),
|
||||||
globals.fs.directory(Cache.flutterRoot),
|
);
|
||||||
pubspecs,
|
// Create a synthetic flutter SDK so that transitive flutter SDK
|
||||||
);
|
// constraints are not affected by this upgrade.
|
||||||
}
|
Directory temporaryFlutterSdk;
|
||||||
|
if (upgrade) {
|
||||||
// Next we run "pub upgrade" on this generated package:
|
temporaryFlutterSdk = createTemporaryFlutterSdk(
|
||||||
await pub.get(
|
globals.logger,
|
||||||
context: PubContext.updatePackages,
|
globals.fs,
|
||||||
directory: tempDir.path,
|
globals.fs.directory(Cache.flutterRoot),
|
||||||
upgrade: true,
|
pubspecs,
|
||||||
offline: offline,
|
|
||||||
flutterRootOverride: upgrade
|
|
||||||
? temporaryFlutterSdk.path
|
|
||||||
: null,
|
|
||||||
generateSyntheticPackage: false,
|
|
||||||
);
|
);
|
||||||
// Cleanup the temporary SDK
|
}
|
||||||
try {
|
|
||||||
temporaryFlutterSdk?.deleteSync(recursive: true);
|
|
||||||
} on FileSystemException {
|
|
||||||
// Failed to delete temporary SDK.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then we run "pub deps --style=compact" on the result. We pipe all the
|
// Next we run "pub upgrade" on this generated package, if we're doing
|
||||||
// output to tree.fill(), which parses it so that it can create a graph
|
// an upgrade. Otherwise, we just run a regular "pub get" on it in order
|
||||||
// of all the dependencies so that we can figure out the transitive
|
// to force the download of any needed packages to the pub cache.
|
||||||
// dependencies later. It also remembers which version was selected for
|
await pub.get(
|
||||||
// each package.
|
context: PubContext.updatePackages,
|
||||||
|
directory: tempDir.path,
|
||||||
|
upgrade: doUpgrade,
|
||||||
|
offline: offline,
|
||||||
|
flutterRootOverride: upgrade
|
||||||
|
? temporaryFlutterSdk.path
|
||||||
|
: null,
|
||||||
|
generateSyntheticPackage: false,
|
||||||
|
);
|
||||||
|
// Cleanup the temporary SDK
|
||||||
|
try {
|
||||||
|
temporaryFlutterSdk?.deleteSync(recursive: true);
|
||||||
|
} on FileSystemException {
|
||||||
|
// Failed to delete temporary SDK.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doUpgrade) {
|
||||||
|
// If upgrading, we run "pub deps --style=compact" on the result. We
|
||||||
|
// pipe all the output to tree.fill(), which parses it so that it can
|
||||||
|
// create a graph of all the dependencies so that we can figure out the
|
||||||
|
// transitive dependencies later. It also remembers which version was
|
||||||
|
// selected for each package.
|
||||||
await pub.batch(
|
await pub.batch(
|
||||||
<String>['deps', '--style=compact'],
|
<String>['deps', '--style=compact'],
|
||||||
context: PubContext.updatePackages,
|
context: PubContext.updatePackages,
|
||||||
@ -376,10 +389,12 @@ class UpdatePackagesCommand extends FlutterCommand {
|
|||||||
filter: tree.fill,
|
filter: tree.fill,
|
||||||
retry: false, // errors here are usually fatal since we're not hitting the network
|
retry: false, // errors here are usually fatal since we're not hitting the network
|
||||||
);
|
);
|
||||||
} finally {
|
|
||||||
tempDir.deleteSync(recursive: true);
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
tempDir.deleteSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doUpgrade) {
|
||||||
// The transitive dependency tree for the fake package does not contain
|
// The transitive dependency tree for the fake package does not contain
|
||||||
// dependencies between Flutter SDK packages and pub packages. We add them
|
// dependencies between Flutter SDK packages and pub packages. We add them
|
||||||
// here.
|
// here.
|
||||||
@ -429,20 +444,53 @@ class UpdatePackagesCommand extends FlutterCommand {
|
|||||||
final Stopwatch timer = Stopwatch()..start();
|
final Stopwatch timer = Stopwatch()..start();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
for (final Directory dir in packages) {
|
// Now we run pub get on each of the affected packages to update their
|
||||||
await pub.get(
|
// pubspec.lock files with the right transitive dependencies.
|
||||||
context: PubContext.updatePackages,
|
//
|
||||||
directory: dir.path,
|
// This can be expensive, so we run them in parallel. If we hadn't already
|
||||||
offline: offline,
|
// warmed the cache above, running them in parallel could be dangerous due
|
||||||
generateSyntheticPackage: false,
|
// to contention when unpacking downloaded dependencies, but since we have
|
||||||
);
|
// downloaded all that we need, it is safe to run them in parallel.
|
||||||
count += 1;
|
final Status status = globals.logger.startProgress(
|
||||||
|
'Running "flutter pub get" in affected packages...',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
final TaskQueue<void> queue = TaskQueue<void>();
|
||||||
|
for (final Directory dir in packages) {
|
||||||
|
unawaited(queue.add(() async {
|
||||||
|
final Stopwatch stopwatch = Stopwatch();
|
||||||
|
stopwatch.start();
|
||||||
|
await pub.get(
|
||||||
|
context: PubContext.updatePackages,
|
||||||
|
directory: dir.path,
|
||||||
|
offline: offline,
|
||||||
|
generateSyntheticPackage: false,
|
||||||
|
printProgress: false,
|
||||||
|
);
|
||||||
|
stopwatch.stop();
|
||||||
|
final double seconds = stopwatch.elapsedMilliseconds / 1000.0;
|
||||||
|
final String relativeDir = globals.fs.path.relative(dir.path, from: Cache.flutterRoot);
|
||||||
|
globals.printStatus('Ran pub get in $relativeDir in ${seconds.toStringAsFixed(1)}s...');
|
||||||
|
}));
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
unawaited(queue.add(() async {
|
||||||
|
final Stopwatch stopwatch = Stopwatch();
|
||||||
|
await _downloadCoverageData();
|
||||||
|
stopwatch.stop();
|
||||||
|
final double seconds = stopwatch.elapsedMilliseconds / 1000.0;
|
||||||
|
globals.printStatus('Downloaded lcov data for package:flutter in ${seconds.toStringAsFixed(1)}s...');
|
||||||
|
}));
|
||||||
|
await queue.tasksComplete;
|
||||||
|
status?.stop();
|
||||||
|
// The exception is rethrown, so don't catch only Exceptions.
|
||||||
|
} catch (exception) { // ignore: avoid_catches_without_on_clauses
|
||||||
|
status?.cancel();
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _downloadCoverageData();
|
|
||||||
|
|
||||||
final double seconds = timer.elapsedMilliseconds / 1000.0;
|
final double seconds = timer.elapsedMilliseconds / 1000.0;
|
||||||
globals.printStatus("\nRan 'pub' $count time${count == 1 ? "" : "s"} and fetched coverage data in ${seconds.toStringAsFixed(1)}s.");
|
globals.printStatus("\nRan 'pub get' $count time${count == 1 ? "" : "s"} and fetched coverage data in ${seconds.toStringAsFixed(1)}s.");
|
||||||
|
|
||||||
return FlutterCommandResult.success();
|
return FlutterCommandResult.success();
|
||||||
}
|
}
|
||||||
@ -1221,7 +1269,8 @@ class PubspecDependency extends PubspecLine {
|
|||||||
|
|
||||||
/// This generates the entry for this dependency for the pubspec.yaml for the
|
/// This generates the entry for this dependency for the pubspec.yaml for the
|
||||||
/// fake package that we'll use to get the version numbers figured out.
|
/// fake package that we'll use to get the version numbers figured out.
|
||||||
void describeForFakePubspec(StringBuffer dependencies, StringBuffer overrides) {
|
void describeForFakePubspec(StringBuffer dependencies, StringBuffer overrides, { bool useAnyVersion = true}) {
|
||||||
|
final String versionToUse = useAnyVersion || version.isEmpty ? 'any' : version;
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case DependencyKind.unknown:
|
case DependencyKind.unknown:
|
||||||
case DependencyKind.overridden:
|
case DependencyKind.overridden:
|
||||||
@ -1229,12 +1278,12 @@ class PubspecDependency extends PubspecLine {
|
|||||||
break;
|
break;
|
||||||
case DependencyKind.normal:
|
case DependencyKind.normal:
|
||||||
if (!_kManuallyPinnedDependencies.containsKey(name)) {
|
if (!_kManuallyPinnedDependencies.containsKey(name)) {
|
||||||
dependencies.writeln(' $name: any');
|
dependencies.writeln(' $name: $versionToUse');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DependencyKind.path:
|
case DependencyKind.path:
|
||||||
if (_lockIsOverride) {
|
if (_lockIsOverride) {
|
||||||
dependencies.writeln(' $name: any');
|
dependencies.writeln(' $name: $versionToUse');
|
||||||
overrides.writeln(' $name:');
|
overrides.writeln(' $name:');
|
||||||
overrides.writeln(' path: $lockTarget');
|
overrides.writeln(' path: $lockTarget');
|
||||||
} else {
|
} else {
|
||||||
@ -1244,7 +1293,7 @@ class PubspecDependency extends PubspecLine {
|
|||||||
break;
|
break;
|
||||||
case DependencyKind.sdk:
|
case DependencyKind.sdk:
|
||||||
if (_lockIsOverride) {
|
if (_lockIsOverride) {
|
||||||
dependencies.writeln(' $name: any');
|
dependencies.writeln(' $name: $versionToUse');
|
||||||
overrides.writeln(' $name:');
|
overrides.writeln(' $name:');
|
||||||
overrides.writeln(' sdk: $lockTarget');
|
overrides.writeln(' sdk: $lockTarget');
|
||||||
} else {
|
} else {
|
||||||
@ -1254,7 +1303,7 @@ class PubspecDependency extends PubspecLine {
|
|||||||
break;
|
break;
|
||||||
case DependencyKind.git:
|
case DependencyKind.git:
|
||||||
if (_lockIsOverride) {
|
if (_lockIsOverride) {
|
||||||
dependencies.writeln(' $name: any');
|
dependencies.writeln(' $name: $versionToUse');
|
||||||
overrides.writeln(' $name:');
|
overrides.writeln(' $name:');
|
||||||
overrides.writeln(lockLine);
|
overrides.writeln(lockLine);
|
||||||
} else {
|
} else {
|
||||||
@ -1263,6 +1312,11 @@ class PubspecDependency extends PubspecLine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '$name: $version';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates the File object for the pubspec.yaml file of a given Directory.
|
/// Generates the File object for the pubspec.yaml file of a given Directory.
|
||||||
@ -1273,16 +1327,22 @@ File _pubspecFor(Directory directory) {
|
|||||||
|
|
||||||
/// Generates the source of a fake pubspec.yaml file given a list of
|
/// Generates the source of a fake pubspec.yaml file given a list of
|
||||||
/// dependencies.
|
/// dependencies.
|
||||||
String _generateFakePubspec(Iterable<PubspecDependency> dependencies) {
|
String _generateFakePubspec(
|
||||||
|
Iterable<PubspecDependency> dependencies, {
|
||||||
|
bool useAnyVersion = false
|
||||||
|
}) {
|
||||||
final StringBuffer result = StringBuffer();
|
final StringBuffer result = StringBuffer();
|
||||||
final StringBuffer overrides = StringBuffer();
|
final StringBuffer overrides = StringBuffer();
|
||||||
|
final bool verbose = useAnyVersion;
|
||||||
result.writeln('name: flutter_update_packages');
|
result.writeln('name: flutter_update_packages');
|
||||||
result.writeln('environment:');
|
result.writeln('environment:');
|
||||||
result.writeln(" sdk: '>=2.10.0 <3.0.0'");
|
result.writeln(" sdk: '>=2.10.0 <3.0.0'");
|
||||||
result.writeln('dependencies:');
|
result.writeln('dependencies:');
|
||||||
overrides.writeln('dependency_overrides:');
|
overrides.writeln('dependency_overrides:');
|
||||||
if (_kManuallyPinnedDependencies.isNotEmpty) {
|
if (_kManuallyPinnedDependencies.isNotEmpty) {
|
||||||
globals.printStatus('WARNING: the following packages use hard-coded version constraints:');
|
if (verbose) {
|
||||||
|
globals.printStatus('WARNING: the following packages use hard-coded version constraints:');
|
||||||
|
}
|
||||||
final Set<String> allTransitive = <String>{
|
final Set<String> allTransitive = <String>{
|
||||||
for (final PubspecDependency dependency in dependencies)
|
for (final PubspecDependency dependency in dependencies)
|
||||||
dependency.name,
|
dependency.name,
|
||||||
@ -1290,17 +1350,21 @@ String _generateFakePubspec(Iterable<PubspecDependency> dependencies) {
|
|||||||
for (final String package in _kManuallyPinnedDependencies.keys) {
|
for (final String package in _kManuallyPinnedDependencies.keys) {
|
||||||
// Don't add pinned dependency if it is not in the set of all transitive dependencies.
|
// Don't add pinned dependency if it is not in the set of all transitive dependencies.
|
||||||
if (!allTransitive.contains(package)) {
|
if (!allTransitive.contains(package)) {
|
||||||
globals.printStatus('Skipping $package because it was not transitive');
|
if (verbose) {
|
||||||
|
globals.printStatus('Skipping $package because it was not transitive');
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String version = _kManuallyPinnedDependencies[package];
|
final String version = _kManuallyPinnedDependencies[package];
|
||||||
result.writeln(' $package: $version');
|
result.writeln(' $package: $version');
|
||||||
globals.printStatus(' - $package: $version');
|
if (verbose) {
|
||||||
|
globals.printStatus(' - $package: $version');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (final PubspecDependency dependency in dependencies) {
|
for (final PubspecDependency dependency in dependencies) {
|
||||||
if (!dependency.pointsToSdk) {
|
if (!dependency.pointsToSdk) {
|
||||||
dependency.describeForFakePubspec(result, overrides);
|
dependency.describeForFakePubspec(result, overrides, useAnyVersion: useAnyVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.write(overrides.toString());
|
result.write(overrides.toString());
|
||||||
|
@ -104,6 +104,7 @@ abstract class Pub {
|
|||||||
String flutterRootOverride,
|
String flutterRootOverride,
|
||||||
bool checkUpToDate = false,
|
bool checkUpToDate = false,
|
||||||
bool shouldSkipThirdPartyGenerator = true,
|
bool shouldSkipThirdPartyGenerator = true,
|
||||||
|
bool printProgress = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Runs pub in 'batch' mode.
|
/// Runs pub in 'batch' mode.
|
||||||
@ -179,6 +180,7 @@ class _DefaultPub implements Pub {
|
|||||||
String? flutterRootOverride,
|
String? flutterRootOverride,
|
||||||
bool checkUpToDate = false,
|
bool checkUpToDate = false,
|
||||||
bool shouldSkipThirdPartyGenerator = true,
|
bool shouldSkipThirdPartyGenerator = true,
|
||||||
|
bool printProgress = true,
|
||||||
}) async {
|
}) async {
|
||||||
directory ??= _fileSystem.currentDirectory.path;
|
directory ??= _fileSystem.currentDirectory.path;
|
||||||
final File packageConfigFile = _fileSystem.file(
|
final File packageConfigFile = _fileSystem.file(
|
||||||
@ -232,9 +234,9 @@ class _DefaultPub implements Pub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String command = upgrade ? 'upgrade' : 'get';
|
final String command = upgrade ? 'upgrade' : 'get';
|
||||||
final Status status = _logger.startProgress(
|
final Status? status = printProgress ? _logger.startProgress(
|
||||||
'Running "flutter pub $command" in ${_fileSystem.path.basename(directory)}...',
|
'Running "flutter pub $command" in ${_fileSystem.path.basename(directory)}...',
|
||||||
);
|
) : null;
|
||||||
final bool verbose = _logger.isVerbose;
|
final bool verbose = _logger.isVerbose;
|
||||||
final List<String> args = <String>[
|
final List<String> args = <String>[
|
||||||
if (verbose)
|
if (verbose)
|
||||||
@ -257,10 +259,10 @@ class _DefaultPub implements Pub {
|
|||||||
retry: !offline,
|
retry: !offline,
|
||||||
flutterRootOverride: flutterRootOverride,
|
flutterRootOverride: flutterRootOverride,
|
||||||
);
|
);
|
||||||
status.stop();
|
status?.stop();
|
||||||
// The exception is rethrown, so don't catch only Exceptions.
|
// The exception is rethrown, so don't catch only Exceptions.
|
||||||
} catch (exception) { // ignore: avoid_catches_without_on_clauses
|
} catch (exception) { // ignore: avoid_catches_without_on_clauses
|
||||||
status.cancel();
|
status?.cancel();
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,5 +119,6 @@ class FakePub extends Fake implements Pub {
|
|||||||
String flutterRootOverride,
|
String flutterRootOverride,
|
||||||
bool checkUpToDate = false,
|
bool checkUpToDate = false,
|
||||||
bool shouldSkipThirdPartyGenerator = true,
|
bool shouldSkipThirdPartyGenerator = true,
|
||||||
|
bool printProgress = true,
|
||||||
}) async { }
|
}) async { }
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,7 @@ class FakePub extends Fake implements Pub {
|
|||||||
String flutterRootOverride,
|
String flutterRootOverride,
|
||||||
bool checkUpToDate = false,
|
bool checkUpToDate = false,
|
||||||
bool shouldSkipThirdPartyGenerator = true,
|
bool shouldSkipThirdPartyGenerator = true,
|
||||||
|
bool printProgress = true,
|
||||||
}) async {
|
}) async {
|
||||||
fileSystem.currentDirectory
|
fileSystem.currentDirectory
|
||||||
.childDirectory('.dart_tool')
|
.childDirectory('.dart_tool')
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_tools/src/base/task_queue.dart';
|
||||||
|
|
||||||
|
import '../../src/common.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('TaskQueue', () {
|
||||||
|
/// A special test designed to check shared [TaskQueue]
|
||||||
|
/// behavior when exceptions occur after a delay in the passed closures to
|
||||||
|
/// [TaskQueue.add].
|
||||||
|
test('no deadlock when delayed exceptions fire in closures', () async {
|
||||||
|
final TaskQueue<void> sharedTracker = TaskQueue<void>(maxJobs: 2);
|
||||||
|
expect(() async {
|
||||||
|
final Future<void> t = Future<void>.delayed(const Duration(milliseconds: 10), () => throw TestException());
|
||||||
|
await sharedTracker.add(() => t);
|
||||||
|
return t;
|
||||||
|
}, throwsA(const TypeMatcher<TestException>()));
|
||||||
|
expect(() async {
|
||||||
|
final Future<void> t = Future<void>.delayed(const Duration(milliseconds: 10), () => throw TestException());
|
||||||
|
await sharedTracker.add(() => t);
|
||||||
|
return t;
|
||||||
|
}, throwsA(const TypeMatcher<TestException>()));
|
||||||
|
expect(() async {
|
||||||
|
final Future<void> t = Future<void>.delayed(const Duration(milliseconds: 10), () => throw TestException());
|
||||||
|
await sharedTracker.add(() => t);
|
||||||
|
return t;
|
||||||
|
}, throwsA(const TypeMatcher<TestException>()));
|
||||||
|
expect(() async {
|
||||||
|
final Future<void> t = Future<void>.delayed(const Duration(milliseconds: 10), () => throw TestException());
|
||||||
|
await sharedTracker.add(() => t);
|
||||||
|
return t;
|
||||||
|
}, throwsA(const TypeMatcher<TestException>()));
|
||||||
|
|
||||||
|
/// We deadlock here if the exception is not handled properly.
|
||||||
|
await sharedTracker.tasksComplete;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic sequential processing works with no deadlock', () async {
|
||||||
|
final Set<int> completed = <int>{};
|
||||||
|
final TaskQueue<void> tracker = TaskQueue<void>(maxJobs: 1);
|
||||||
|
await tracker.add(() async => completed.add(1));
|
||||||
|
await tracker.add(() async => completed.add(2));
|
||||||
|
await tracker.add(() async => completed.add(3));
|
||||||
|
await tracker.tasksComplete;
|
||||||
|
expect(completed.length, equals(3));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic sequential processing works on exceptions', () async {
|
||||||
|
final Set<int> completed = <int>{};
|
||||||
|
final TaskQueue<void> tracker = TaskQueue<void>(maxJobs: 1);
|
||||||
|
await tracker.add(() async => completed.add(0));
|
||||||
|
await tracker.add(() async => throw TestException()).catchError((Object _) {});
|
||||||
|
await tracker.add(() async => throw TestException()).catchError((Object _) {});
|
||||||
|
await tracker.add(() async => completed.add(3));
|
||||||
|
await tracker.tasksComplete;
|
||||||
|
expect(completed.length, equals(2));
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Verify that if there are more exceptions than the maximum number
|
||||||
|
/// of in-flight [Future]s that there is no deadlock.
|
||||||
|
test('basic parallel processing works with no deadlock', () async {
|
||||||
|
final Set<int> completed = <int>{};
|
||||||
|
final TaskQueue<void> tracker = TaskQueue<void>(maxJobs: 10);
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
await tracker.add(() async => completed.add(i));
|
||||||
|
}
|
||||||
|
await tracker.tasksComplete;
|
||||||
|
expect(completed.length, equals(100));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic parallel processing works on exceptions', () async {
|
||||||
|
final Set<int> completed = <int>{};
|
||||||
|
final TaskQueue<void> tracker = TaskQueue<void>(maxJobs: 10);
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
await tracker.add(() async => completed.add(i));
|
||||||
|
}
|
||||||
|
for (int i = 50; i < 65; i++) {
|
||||||
|
try {
|
||||||
|
await tracker.add(() async => throw TestException());
|
||||||
|
} on TestException {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 65; i < 100; i++) {
|
||||||
|
await tracker.add(() async => completed.add(i));
|
||||||
|
}
|
||||||
|
await tracker.tasksComplete;
|
||||||
|
expect(completed.length, equals(85));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestException implements Exception {}
|
@ -1073,6 +1073,7 @@ class FakePub extends Fake implements Pub {
|
|||||||
String flutterRootOverride,
|
String flutterRootOverride,
|
||||||
bool checkUpToDate = false,
|
bool checkUpToDate = false,
|
||||||
bool shouldSkipThirdPartyGenerator = true,
|
bool shouldSkipThirdPartyGenerator = true,
|
||||||
|
bool printProgress = true,
|
||||||
}) async {
|
}) async {
|
||||||
calledGet += 1;
|
calledGet += 1;
|
||||||
}
|
}
|
||||||
|
@ -723,5 +723,6 @@ class FakePub extends Fake implements Pub {
|
|||||||
String flutterRootOverride,
|
String flutterRootOverride,
|
||||||
bool checkUpToDate = false,
|
bool checkUpToDate = false,
|
||||||
bool shouldSkipThirdPartyGenerator = true,
|
bool shouldSkipThirdPartyGenerator = true,
|
||||||
|
bool printProgress = true,
|
||||||
}) async { }
|
}) async { }
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ class ThrowingPub implements Pub {
|
|||||||
String? flutterRootOverride,
|
String? flutterRootOverride,
|
||||||
bool checkUpToDate = false,
|
bool checkUpToDate = false,
|
||||||
bool shouldSkipThirdPartyGenerator = true,
|
bool shouldSkipThirdPartyGenerator = true,
|
||||||
|
bool printProgress = true,
|
||||||
}) {
|
}) {
|
||||||
throw UnsupportedError('Attempted to invoke pub during test.');
|
throw UnsupportedError('Attempted to invoke pub during test.');
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user