blob: 01e10c2cd9c02ace6ac49a75c55fcfd63d84c20e [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 '../entrypoint.dart';
import '../io.dart';
import '../log.dart' as log;
import '../package.dart';
import '../pubspec.dart';
import '../solver.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 a dependency from the current package.';
@override
String get argumentsDescription => '<package>';
@override
String get docUrl => 'https://dart.dev/tools/pub/cmd/pub-remove';
@override
bool get isOffline => argResults['offline'];
bool get isDryRun => argResults['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',
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 packages = Set<String>.from(argResults.rest);
if (isDryRun) {
final rootPubspec = entrypoint.root.pubspec;
final newPubspec = _removePackagesFromPubspec(rootPubspec, packages);
final newRoot = Package.inMemory(newPubspec);
await Entrypoint.inMemory(newRoot, cache, lockFile: entrypoint.lockFile)
.acquireDependencies(
SolveType.get,
precompile: argResults['precompile'],
dryRun: true,
analytics: null,
);
} else {
/// Update the pubspec.
_writeRemovalToPubspec(packages);
/// Create a new [Entrypoint] since we have to reprocess the updated
/// pubspec file.
final updatedEntrypoint = Entrypoint(directory, cache);
await updatedEntrypoint.acquireDependencies(
SolveType.get,
precompile: argResults['precompile'],
analytics: analytics,
);
var example = entrypoint.example;
if (argResults['example'] && example != null) {
await example.acquireDependencies(
SolveType.get,
precompile: argResults['precompile'],
summaryOnly: true,
analytics: analytics,
);
}
}
}
Pubspec _removePackagesFromPubspec(Pubspec original, Set<String> packages) {
final originalDependencies = original.dependencies.values;
final originalDevDependencies = original.devDependencies.values;
final newDependencies = originalDependencies
.where((dependency) => !packages.contains(dependency.name));
final newDevDependencies = originalDevDependencies
.where((dependency) => !packages.contains(dependency.name));
return Pubspec(
original.name,
version: original.version,
sdkConstraints: original.sdkConstraints,
dependencies: newDependencies,
devDependencies: newDevDependencies,
dependencyOverrides: original.dependencyOverrides.values,
);
}
/// Writes the changes to the pubspec file
void _writeRemovalToPubspec(Set<String> packages) {
ArgumentError.checkNotNull(packages, 'packages');
final yamlEditor = YamlEditor(readTextFile(entrypoint.pubspecPath));
for (var package in packages) {
var found = false;
/// There may be packages where the dependency is declared both in
/// dependencies and dev_dependencies.
for (final dependencyKey in ['dependencies', 'dev_dependencies']) {
final dependenciesNode = yamlEditor
.parseAt([dependencyKey], orElse: () => YamlScalar.wrap(null));
if (dependenciesNode is YamlMap &&
dependenciesNode.containsKey(package)) {
yamlEditor.remove([dependencyKey, package]);
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 "$package" was not found in pubspec.yaml!');
}
/// Windows line endings are already handled by [yamlEditor]
writeTextFile(entrypoint.pubspecPath, yamlEditor.toString());
}
}
}