clean.dart 5.07 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'package:meta/meta.dart';

7
import '../../src/macos/xcode.dart';
8
import '../base/common.dart';
9
import '../base/file_system.dart';
10
import '../base/logger.dart';
11
import '../build_info.dart';
12
import '../globals.dart' as globals;
13
import '../ios/xcodeproj.dart';
14
import '../project.dart';
15 16 17
import '../runner/flutter_command.dart';

class CleanCommand extends FlutterCommand {
18 19 20
  CleanCommand({
    bool verbose = false,
  }) : _verbose = verbose {
21
    requiresPubspecYaml();
22 23 24 25
    argParser.addOption(
      'scheme',
      help: 'When cleaning Xcode schemes, clean only the specified scheme.',
    );
26 27
  }

28 29
  final bool _verbose;

30 31 32 33
  @override
  final String name = 'clean';

  @override
34
  final String description = 'Delete the build/ and .dart_tool/ directories.';
35

36 37 38
  @override
  String get category => FlutterCommandCategory.project;

39 40 41
  @override
  Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};

42
  @override
43
  Future<FlutterCommandResult> runCommand() async {
44 45 46
    // Clean Xcode to remove intermediate DerivedData artifacts.
    // Do this before removing ephemeral directory, which would delete the xcworkspace.
    final FlutterProject flutterProject = FlutterProject.current();
47 48
    final Xcode? xcode = globals.xcode;
    if (xcode != null && xcode.isInstalledAndMeetsVersionCheck) {
49 50 51 52
      await _cleanXcode(flutterProject.ios);
      await _cleanXcode(flutterProject.macos);
    }

53
    final Directory buildDir = globals.fs.directory(getBuildDirectory());
54
    deleteFile(buildDir);
55

56
    deleteFile(flutterProject.dartTool);
57
    deleteFile(flutterProject.packagesFile);
58

59
    deleteFile(flutterProject.android.ephemeralDirectory);
60

61
    deleteFile(flutterProject.ios.ephemeralDirectory);
62
    deleteFile(flutterProject.ios.ephemeralModuleDirectory);
63 64
    deleteFile(flutterProject.ios.generatedXcodePropertiesFile);
    deleteFile(flutterProject.ios.generatedEnvironmentVariableExportScript);
65
    deleteFile(flutterProject.ios.deprecatedCompiledDartFramework);
66
    deleteFile(flutterProject.ios.deprecatedProjectFlutterFramework);
67
    deleteFile(flutterProject.ios.flutterPodspec);
68

69 70 71
    deleteFile(flutterProject.linux.ephemeralDirectory);
    deleteFile(flutterProject.macos.ephemeralDirectory);
    deleteFile(flutterProject.windows.ephemeralDirectory);
72 73
    deleteFile(flutterProject.flutterPluginsDependenciesFile);
    deleteFile(flutterProject.flutterPluginsFile);
74

75 76 77
    return const FlutterCommandResult(ExitStatus.success);
  }

78
  Future<void> _cleanXcode(XcodeBasedProject xcodeProject) async {
79 80
    final Directory? xcodeWorkspace = xcodeProject.xcodeWorkspace;
    if (xcodeWorkspace == null) {
81 82
      return;
    }
83 84 85
    final Status xcodeStatus = globals.logger.startProgress(
      'Cleaning Xcode workspace...',
    );
86
    try {
87
      final XcodeProjectInterpreter xcodeProjectInterpreter = globals.xcodeProjectInterpreter!;
88
      final XcodeProjectInfo projectInfo = (await xcodeProjectInterpreter.getInfo(xcodeWorkspace.parent.path))!;
89 90 91 92 93 94 95 96
      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}');
        }
97
        await xcodeProjectInterpreter.cleanWorkspace(xcodeWorkspace.path, scheme, verbose: _verbose);
98 99 100 101
      } else {
        for (final String scheme in projectInfo.schemes) {
          await xcodeProjectInterpreter.cleanWorkspace(xcodeWorkspace.path, scheme, verbose: _verbose);
        }
102
      }
103
    } on Exception catch (error) {
104 105 106 107 108 109
      final String message = 'Could not clean Xcode workspace: $error';
      if (argResults?.wasParsed('scheme') ?? false) {
        throwToolExit(message);
      } else {
        globals.printTrace(message);
      }
110
    } finally {
111
      xcodeStatus.stop();
112 113 114 115 116
    }
  }

  @visibleForTesting
  void deleteFile(FileSystemEntity file) {
117 118 119 120 121 122
    // This will throw a FileSystemException if the directory is missing permissions.
    try {
      if (!file.existsSync()) {
        return;
      }
    } on FileSystemException catch (err) {
123
      globals.printError('Cannot clean ${file.path}.\n$err');
124 125
      return;
    }
126 127 128
    final Status deletionStatus = globals.logger.startProgress(
      'Deleting ${file.basename}...',
    );
129 130 131 132
    try {
      file.deleteSync(recursive: true);
    } on FileSystemException catch (error) {
      final String path = file.path;
133
      if (globals.platform.isWindows) {
134
        globals.printError('Failed to remove $path. '
135 136 137
            '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');
138
      } else {
139
        globals.printError('Failed to remove $path: $error');
140
      }
141 142
    } finally {
      deletionStatus.stop();
143
    }
144
  }
145
}