assemble.dart 8.46 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 '../base/common.dart';
8
import '../base/file_system.dart';
9
import '../build_system/build_system.dart';
10 11
import '../build_system/depfile.dart';
import '../build_system/targets/android.dart';
12 13 14 15 16
import '../build_system/targets/assets.dart';
import '../build_system/targets/dart.dart';
import '../build_system/targets/ios.dart';
import '../build_system/targets/linux.dart';
import '../build_system/targets/macos.dart';
17
import '../build_system/targets/web.dart';
18
import '../build_system/targets/windows.dart';
19
import '../cache.dart';
20
import '../globals.dart' as globals;
21
import '../project.dart';
22
import '../reporting/reporting.dart';
23 24
import '../runner/flutter_command.dart';

25 26 27 28 29 30 31 32 33
/// All currently implemented targets.
const List<Target> _kDefaultTargets = <Target>[
  UnpackWindows(),
  CopyAssets(),
  KernelSnapshot(),
  AotElfProfile(),
  AotElfRelease(),
  AotAssemblyProfile(),
  AotAssemblyRelease(),
34
  DebugMacOSFramework(),
35 36 37
  DebugMacOSBundleFlutterAssets(),
  ProfileMacOSBundleFlutterAssets(),
  ReleaseMacOSBundleFlutterAssets(),
38
  DebugBundleLinuxAssets(),
39
  WebServiceWorker(),
40
  DebugAndroidApplication(),
41
  FastStartAndroidApplication(),
42 43 44 45 46 47
  ProfileAndroidApplication(),
  ReleaseAndroidApplication(),
  // These are one-off rules for bundle and aot compat
  ReleaseCopyFlutterAotBundle(),
  ProfileCopyFlutterAotBundle(),
  CopyFlutterBundle(),
48 49 50 51 52 53 54
  // Android ABI specific AOT rules.
  androidArmProfileBundle,
  androidArm64ProfileBundle,
  androidx64ProfileBundle,
  androidArmReleaseBundle,
  androidArm64ReleaseBundle,
  androidx64ReleaseBundle,
55 56
];

57 58 59 60 61 62 63
/// Assemble provides a low level API to interact with the flutter tool build
/// system.
class AssembleCommand extends FlutterCommand {
  AssembleCommand() {
    argParser.addMultiOption(
      'define',
      abbr: 'd',
64
      help: 'Allows passing configuration to a target with --define=target=key=value.',
65
    );
66 67 68
    argParser.addOption('depfile', help: 'A file path where a depfile will be written. '
      'This contains all build inputs and outputs in a make style syntax'
    );
69 70 71 72 73 74 75 76
    argParser.addOption('build-inputs', help: 'A file path where a newline '
        'separated file containing all inputs used will be written after a build.'
        ' This file is not included as a build input or output. This file is not'
        ' written if the build fails for any reason.');
    argParser.addOption('build-outputs', help: 'A file path where a newline '
        'separated file containing all outputs used will be written after a build.'
        ' This file is not included as a build input or output. This file is not'
        ' written if the build fails for any reason.');
77 78 79 80
    argParser.addOption('output', abbr: 'o', help: 'A directory where output '
        'files will be written. Must be either absolute or relative from the '
        'root of the current Flutter project.',
    );
81
    argParser.addOption(kExtraGenSnapshotOptions);
82 83
    argParser.addOption(
      'resource-pool-size',
84
      help: 'The maximum number of concurrent tasks the build system will run.',
85 86 87
    );
  }

88 89
  @override
  String get description => 'Assemble and build flutter resources.';
90

91 92
  @override
  String get name => 'assemble';
93

94 95 96 97 98 99
  @override
  Future<Map<CustomDimensions, String>> get usageValues async {
    final FlutterProject futterProject = FlutterProject.current();
    if (futterProject == null) {
      return const <CustomDimensions, String>{};
    }
100 101 102 103 104 105 106 107 108 109
    try {
      final Environment localEnvironment = environment;
      return <CustomDimensions, String>{
        CustomDimensions.commandBuildBundleTargetPlatform: localEnvironment.defines['TargetPlatform'],
        CustomDimensions.commandBuildBundleIsModule: '${futterProject.isModule}',
      };
    } catch (err) {
      // We've failed to send usage.
    }
    return const <CustomDimensions, String>{};
110 111
  }

112 113
  /// The target(s) we are building.
  List<Target> get targets {
114 115 116
    if (argResults.rest.isEmpty) {
      throwToolExit('missing target name for flutter assemble.');
    }
117
    final String name = argResults.rest.first;
118
    final Map<String, Target> targetMap = <String, Target>{
119
      for (final Target target in _kDefaultTargets)
120 121 122
        target.name: target
    };
    final List<Target> results = <Target>[
123
      for (final String targetName in argResults.rest)
124 125 126 127
        if (targetMap.containsKey(targetName))
          targetMap[targetName]
    ];
    if (results.isEmpty) {
128
      throwToolExit('No target named "$name" defined.');
129
    }
130
    return results;
131 132 133 134 135
  }

  /// The environmental configuration for a build invocation.
  Environment get environment {
    final FlutterProject flutterProject = FlutterProject.current();
136
    String output = stringArg('output');
137 138 139 140
    if (output == null) {
      throwToolExit('--output directory is required for assemble.');
    }
    // If path is relative, make it absolute from flutter project.
141 142
    if (globals.fs.path.isRelative(output)) {
      output = globals.fs.path.join(flutterProject.directory.path, output);
143
    }
144
    final Environment result = Environment(
145
      outputDir: globals.fs.directory(output),
146 147 148
      buildDir: flutterProject.directory
          .childDirectory('.dart_tool')
          .childDirectory('flutter_build'),
149
      projectDir: flutterProject.directory,
150
      defines: _parseDefines(stringsArg('define')),
151 152
      cacheDir: globals.cache.getRoot(),
      flutterRootDir: globals.fs.directory(Cache.flutterRoot),
153 154 155 156
    );
    return result;
  }

157
  Map<String, String> _parseDefines(List<String> values) {
158
    final Map<String, String> results = <String, String>{};
159
    for (final String chunk in values) {
160 161
      final int indexEquals = chunk.indexOf('=');
      if (indexEquals == -1) {
162 163
        throwToolExit('Improperly formatted define flag: $chunk');
      }
164 165
      final String key = chunk.substring(0, indexEquals);
      final String value = chunk.substring(indexEquals + 1);
166 167
      results[key] = value;
    }
168 169 170 171
    // Workaround for extraGenSnapshot formatting.
    if (argResults.wasParsed(kExtraGenSnapshotOptions)) {
      results[kExtraGenSnapshotOptions] = argResults[kExtraGenSnapshotOptions] as String;
    }
172 173 174 175 176
    return results;
  }

  @override
  Future<FlutterCommandResult> runCommand() async {
177 178
    final List<Target> targets = this.targets;
    final Target target = targets.length == 1 ? targets.single : _CompositeTarget(targets);
179
    final BuildResult result = await buildSystem.build(target, environment, buildSystemConfig: BuildSystemConfig(
180
      resourcePoolSize: argResults.wasParsed('resource-pool-size')
181
        ? int.tryParse(stringArg('resource-pool-size'))
182
        : null,
183 184
    ));
    if (!result.success) {
185
      for (final ExceptionMeasurement measurement in result.exceptions.values) {
186
        globals.printError('Target ${measurement.target} failed: ${measurement.exception}',
187 188 189 190
          stackTrace: measurement.fatal
            ? measurement.stackTrace
            : null,
        );
191
      }
192 193
      throwToolExit('build failed.');
    }
194
    globals.printTrace('build succeeded.');
195
    if (argResults.wasParsed('build-inputs')) {
196
      writeListIfChanged(result.inputFiles, stringArg('build-inputs'));
197 198
    }
    if (argResults.wasParsed('build-outputs')) {
199
      writeListIfChanged(result.outputFiles, stringArg('build-outputs'));
200
    }
201
    if (argResults.wasParsed('depfile')) {
202
      final File depfileFile = globals.fs.file(stringArg('depfile'));
203
      final Depfile depfile = Depfile(result.inputFiles, result.outputFiles);
204
      depfile.writeToFile(globals.fs.file(depfileFile));
205
    }
206
    return FlutterCommandResult.success();
207 208
  }
}
209 210 211

@visibleForTesting
void writeListIfChanged(List<File> files, String path) {
212
  final File file = globals.fs.file(path);
213 214
  final StringBuffer buffer = StringBuffer();
  // These files are already sorted.
215
  for (final File file in files) {
216
    buffer.writeln(file.path);
217 218 219 220 221 222 223 224 225 226
  }
  final String newContents = buffer.toString();
  if (!file.existsSync()) {
    file.writeAsStringSync(newContents);
  }
  final String currentContents = file.readAsStringSync();
  if (currentContents != newContents) {
    file.writeAsStringSync(newContents);
  }
}
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245

class _CompositeTarget extends Target {
  _CompositeTarget(this.dependencies);

  @override
  final List<Target> dependencies;

  @override
  String get name => '_composite';

  @override
  Future<void> build(Environment environment) async { }

  @override
  List<Source> get inputs => <Source>[];

  @override
  List<Source> get outputs => <Source>[];
}