1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// 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();
}
}
}