blob: 7063c52f730987dd51e1fc08a165b9a95da8cb37 [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:yaml/yaml.dart';
import 'package:yaml_edit/yaml_edit.dart';
import '../command.dart';
import '../io.dart';
import '../log.dart' as log;
import '../pubspec.dart';
import '../solver.dart';
import '../utils.dart';
/// Handles the `remove` pub command. Removes dependencies from `pubspec.yaml`,
/// and performs an operation similar to `pub get`. Unlike `pub add`, this
/// command supports the removal of multiple dependencies.
class RemoveCommand extends PubCommand {
@override
String get name => 'remove';
@override
String get description => '''
Removes dependencies from `pubspec.yaml`.
Invoking `dart pub remove foo bar` will remove `foo` and `bar` from either
`dependencies` or `dev_dependencies` in `pubspec.yaml`.
To remove a dependency override of a package prefix the package name with
'override:'.
''';
@override
String get argumentsDescription => '<package1> [<package2>...]';
@override
String get docUrl => 'https://dart.dev/tools/pub/cmd/pub-remove';
@override
bool get isOffline => argResults.flag('offline');
bool get isDryRun => argResults.flag('dry-run');
RemoveCommand() {
argParser.addFlag(
'offline',
help: 'Use cached packages instead of accessing the network.',
);
argParser.addFlag(
'dry-run',
abbr: 'n',
negatable: false,
help: "Report what dependencies would change but don't change any.",
);
argParser.addFlag(
'precompile',
help: 'Precompile executables in immediate dependencies.',
);
argParser.addFlag(
'example',
defaultsTo: true,
help: 'Also update dependencies in `example/` (if it exists).',
hide: true,
);
argParser.addOption(
'directory',
abbr: 'C',
help: 'Run this in the directory <dir>.',
valueHelp: 'dir',
);
}
@override
Future<void> runProtected() async {
if (argResults.rest.isEmpty) {
usageException('Must specify a package to be removed.');
}
final targets = Set<String>.from(argResults.rest).map((descriptor) {
final isOverride = descriptor.startsWith('override:');
final name =
isOverride ? descriptor.substring('override:'.length) : descriptor;
return _PackageRemoval(name, removeFromOverride: isOverride);
});
if (!isDryRun) {
/// Update the pubspec.
_writeRemovalToPubspec(targets);
}
final rootPubspec = entrypoint.root.pubspec;
final newPubspec = _removePackagesFromPubspec(rootPubspec, targets);
await entrypoint.withPubspec(newPubspec).acquireDependencies(
SolveType.get,
precompile: !isDryRun && argResults.flag('precompile'),
dryRun: isDryRun,
analytics: isDryRun ? null : analytics,
);
var example = entrypoint.example;
if (!isDryRun && argResults.flag('example') && example != null) {
await example.acquireDependencies(
SolveType.get,
precompile: argResults.flag('precompile'),
summaryOnly: true,
analytics: analytics,
);
}
}
Pubspec _removePackagesFromPubspec(
Pubspec original,
Iterable<_PackageRemoval> packages,
) {
final dependencies = {...original.dependencies};
final devDependencies = {...original.devDependencies};
final overrides = {...original.dependencyOverrides};
for (final package in packages) {
if (package.removeFromOverride) {
overrides.remove(package.name);
} else {
dependencies.remove(package.name);
devDependencies.remove(package.name);
}
}
return Pubspec(
original.name,
version: original.version,
sdkConstraints: original.sdkConstraints,
dependencies: dependencies.values,
devDependencies: devDependencies.values,
dependencyOverrides: overrides.values,
);
}
/// Writes the changes to the pubspec file
void _writeRemovalToPubspec(Iterable<_PackageRemoval> packages) {
ArgumentError.checkNotNull(packages, 'packages');
final yamlEditor = YamlEditor(readTextFile(entrypoint.pubspecPath));
for (final package in packages) {
final dependencyKeys = package.removeFromOverride
? ['dependency_overrides']
: ['dependencies', 'dev_dependencies'];
var found = false;
final name = package.name;
/// There may be packages where the dependency is declared both in
/// dependencies and dev_dependencies - remove it from both in that case.
for (final dependencyKey in dependencyKeys) {
final dependenciesNode = yamlEditor
.parseAt([dependencyKey], orElse: () => YamlScalar.wrap(null));
if (dependenciesNode is YamlMap && dependenciesNode.containsKey(name)) {
yamlEditor.remove([dependencyKey, name]);
found = true;
// Check if the dependencies or dev_dependencies map is now empty
// If it is empty, remove the key as well
if ((yamlEditor.parseAt([dependencyKey]) as YamlMap).isEmpty) {
yamlEditor.remove([dependencyKey]);
}
}
}
if (!found) {
log.warning(
'Package "$name" was not found in ${entrypoint.pubspecPath}!',
);
}
}
/// Windows line endings are already handled by [yamlEditor]
writeTextFile(entrypoint.pubspecPath, yamlEditor.toString());
}
}
class _PackageRemoval {
final String name;
final bool removeFromOverride;
_PackageRemoval(this.name, {required this.removeFromOverride});
}