assemble.dart 5.49 KB
Newer Older
1 2 3 4
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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 12 13 14
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';
15
import '../build_system/targets/web.dart';
16
import '../build_system/targets/windows.dart';
17 18 19 20
import '../globals.dart';
import '../project.dart';
import '../runner/flutter_command.dart';

21 22 23 24 25 26 27 28 29
/// All currently implemented targets.
const List<Target> _kDefaultTargets = <Target>[
  UnpackWindows(),
  CopyAssets(),
  KernelSnapshot(),
  AotElfProfile(),
  AotElfRelease(),
  AotAssemblyProfile(),
  AotAssemblyRelease(),
30
  DebugMacOSFramework(),
31 32 33
  DebugMacOSBundleFlutterAssets(),
  ProfileMacOSBundleFlutterAssets(),
  ReleaseMacOSBundleFlutterAssets(),
34
  DebugBundleLinuxAssets(),
35
  WebReleaseBundle(),
36 37
];

38 39 40 41 42 43 44
/// Assemble provides a low level API to interact with the flutter tool build
/// system.
class AssembleCommand extends FlutterCommand {
  AssembleCommand() {
    argParser.addMultiOption(
      'define',
      abbr: 'd',
45
      help: 'Allows passing configuration to a target with --define=target=key=value.',
46
    );
47 48 49 50 51 52 53 54
    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.');
55 56 57 58
    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.',
    );
59 60
    argParser.addOption(
      'resource-pool-size',
61
      help: 'The maximum number of concurrent tasks the build system will run.',
62 63 64
    );
  }

65 66
  @override
  String get description => 'Assemble and build flutter resources.';
67

68 69
  @override
  String get name => 'assemble';
70

71 72
  /// The target we are building.
  Target get target {
73 74 75
    if (argResults.rest.isEmpty) {
      throwToolExit('missing target name for flutter assemble.');
    }
76
    final String name = argResults.rest.first;
77 78 79
    final Target result = _kDefaultTargets
        .firstWhere((Target target) => target.name == name, orElse: () => null);
    if (result == null) {
80
      throwToolExit('No target named "$name" defined.');
81 82
    }
    return result;
83 84 85 86 87
  }

  /// The environmental configuration for a build invocation.
  Environment get environment {
    final FlutterProject flutterProject = FlutterProject.current();
88 89 90 91 92 93 94 95
    String output = argResults['output'];
    if (output == null) {
      throwToolExit('--output directory is required for assemble.');
    }
    // If path is relative, make it absolute from flutter project.
    if (fs.path.isRelative(output)) {
      output = fs.path.join(flutterProject.directory.path, output);
    }
96
    final Environment result = Environment(
97
      outputDir: fs.directory(output),
98 99 100
      buildDir: flutterProject.directory
          .childDirectory('.dart_tool')
          .childDirectory('flutter_build'),
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
      projectDir: flutterProject.directory,
      defines: _parseDefines(argResults['define']),
    );
    return result;
  }

  static Map<String, String> _parseDefines(List<String> values) {
    final Map<String, String> results = <String, String>{};
    for (String chunk in values) {
      final List<String> parts = chunk.split('=');
      if (parts.length != 2) {
        throwToolExit('Improperly formatted define flag: $chunk');
      }
      final String key = parts[0];
      final String value = parts[1];
      results[key] = value;
    }
    return results;
  }

  @override
  Future<FlutterCommandResult> runCommand() async {
123
    final BuildResult result = await buildSystem.build(target, environment, buildSystemConfig: BuildSystemConfig(
124 125 126 127 128 129 130
      resourcePoolSize: argResults['resource-pool-size'],
    ));
    if (!result.success) {
      for (MapEntry<String, ExceptionMeasurement> data in result.exceptions.entries) {
        printError('Target ${data.key} failed: ${data.value.exception}');
        printError('${data.value.exception}');
      }
131 132 133 134 135 136 137 138
      throwToolExit('build failed.');
    }
    printStatus('build succeeded.');
    if (argResults.wasParsed('build-inputs')) {
      writeListIfChanged(result.inputFiles, argResults['build-inputs']);
    }
    if (argResults.wasParsed('build-outputs')) {
      writeListIfChanged(result.outputFiles, argResults['build-outputs']);
139 140 141 142
    }
    return null;
  }
}
143 144 145 146 147 148 149

@visibleForTesting
void writeListIfChanged(List<File> files, String path) {
  final File file = fs.file(path);
  final StringBuffer buffer = StringBuffer();
  // These files are already sorted.
  for (File file in files) {
150
    buffer.writeln(file.path);
151 152 153 154 155 156 157 158 159 160
  }
  final String newContents = buffer.toString();
  if (!file.existsSync()) {
    file.writeAsStringSync(newContents);
  }
  final String currentContents = file.readAsStringSync();
  if (currentContents != newContents) {
    file.writeAsStringSync(newContents);
  }
}