blob: 3cd5d74f3a67687652ed8882ccb6f869a2a5964f [file] [log] [blame]
// 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 'package:meta/meta.dart';
import '../../src/macos/xcode.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../build_info.dart';
import '../globals.dart' as globals;
import '../ios/xcodeproj.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
class CleanCommand extends FlutterCommand {
CleanCommand({
bool verbose = false,
}) : _verbose = verbose {
requiresPubspecYaml();
argParser.addOption(
'scheme',
help: 'When cleaning Xcode schemes, clean only the specified scheme.',
);
}
final bool _verbose;
@override
final String name = 'clean';
@override
final String description = 'Delete the build/ and .dart_tool/ directories.';
@override
String get category => FlutterCommandCategory.project;
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
@override
Future<FlutterCommandResult> runCommand() async {
// Clean Xcode to remove intermediate DerivedData artifacts.
// Do this before removing ephemeral directory, which would delete the xcworkspace.
final FlutterProject flutterProject = FlutterProject.current();
final Xcode? xcode = globals.xcode;
if (xcode != null && xcode.isInstalledAndMeetsVersionCheck) {
await _cleanXcode(flutterProject.ios);
await _cleanXcode(flutterProject.macos);
}
final Directory buildDir = globals.fs.directory(getBuildDirectory());
deleteFile(buildDir);
deleteFile(flutterProject.dartTool);
deleteFile(flutterProject.packagesFile);
deleteFile(flutterProject.android.ephemeralDirectory);
deleteFile(flutterProject.ios.ephemeralDirectory);
deleteFile(flutterProject.ios.ephemeralModuleDirectory);
deleteFile(flutterProject.ios.generatedXcodePropertiesFile);
deleteFile(flutterProject.ios.generatedEnvironmentVariableExportScript);
deleteFile(flutterProject.ios.deprecatedCompiledDartFramework);
deleteFile(flutterProject.ios.deprecatedProjectFlutterFramework);
deleteFile(flutterProject.ios.flutterPodspec);
deleteFile(flutterProject.linux.ephemeralDirectory);
deleteFile(flutterProject.macos.ephemeralDirectory);
deleteFile(flutterProject.windows.ephemeralDirectory);
deleteFile(flutterProject.flutterPluginsDependenciesFile);
deleteFile(flutterProject.flutterPluginsFile);
return const FlutterCommandResult(ExitStatus.success);
}
Future<void> _cleanXcode(XcodeBasedProject xcodeProject) async {
final Directory? xcodeWorkspace = xcodeProject.xcodeWorkspace;
if (xcodeWorkspace == null) {
return;
}
final Status xcodeStatus = globals.logger.startProgress(
'Cleaning Xcode workspace...',
);
try {
final XcodeProjectInterpreter xcodeProjectInterpreter = globals.xcodeProjectInterpreter!;
final XcodeProjectInfo projectInfo = (await xcodeProjectInterpreter.getInfo(xcodeWorkspace.parent.path))!;
if (argResults?.wasParsed('scheme') ?? false) {
final String scheme = argResults!['scheme'] as String;
if (scheme.isEmpty) {
throwToolExit('No scheme was specified for --scheme');
}
if (!projectInfo.schemes.contains(scheme)) {
throwToolExit('Scheme "$scheme" not found in ${projectInfo.schemes}');
}
await xcodeProjectInterpreter.cleanWorkspace(xcodeWorkspace.path, scheme, verbose: _verbose);
} else {
for (final String scheme in projectInfo.schemes) {
await xcodeProjectInterpreter.cleanWorkspace(xcodeWorkspace.path, scheme, verbose: _verbose);
}
}
} on Exception catch (error) {
final String message = 'Could not clean Xcode workspace: $error';
if (argResults?.wasParsed('scheme') ?? false) {
throwToolExit(message);
} else {
globals.printTrace(message);
}
} finally {
xcodeStatus.stop();
}
}
@visibleForTesting
void deleteFile(FileSystemEntity file) {
// This will throw a FileSystemException if the directory is missing permissions.
try {
if (!file.existsSync()) {
return;
}
} on FileSystemException catch (err) {
globals.printError('Cannot clean ${file.path}.\n$err');
return;
}
final Status deletionStatus = globals.logger.startProgress(
'Deleting ${file.basename}...',
);
try {
file.deleteSync(recursive: true);
} on FileSystemException catch (error) {
final String path = file.path;
if (globals.platform.isWindows) {
globals.printError('Failed to remove $path. '
'A program may still be using a file in the directory or the directory itself. '
'To find and stop such a program, see: '
'https://superuser.com/questions/1333118/cant-delete-empty-folder-because-it-is-used');
} else {
globals.printError('Failed to remove $path: $error');
}
} finally {
deletionStatus.stop();
}
}
}