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

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:args/args.dart';
10
import 'package:path/path.dart' as path;
11 12 13 14 15

import 'package:flutter_devicelab/framework/manifest.dart';
import 'package:flutter_devicelab/framework/runner.dart';
import 'package:flutter_devicelab/framework/utils.dart';

16 17
List<String> _taskNames = <String>[];

18 19 20 21
/// Runs tasks.
///
/// The tasks are chosen depending on the command-line options
/// (see [_argParser]).
22
Future<void> main(List<String> rawArgs) async {
23 24 25
  ArgResults args;
  try {
    args = _argParser.parse(rawArgs);
26
  } on FormatException catch (error) {
27 28 29 30
    stderr.writeln('${error.message}\n');
    stderr.writeln('Usage:\n');
    stderr.writeln(_argParser.usage);
    exitCode = 1;
31
    return;
32 33
  }

34
  if (!args.wasParsed('task')) {
35 36 37 38 39 40
    if (args.wasParsed('stage') || args.wasParsed('all')) {
      addTasks(
        tasks: loadTaskManifest().tasks,
        args: args,
        taskNames: _taskNames,
      );
41 42 43
    }
  }

44 45 46 47 48 49 50 51
  if (args.wasParsed('list')) {
    for (int i = 0; i < _taskNames.length; i++) {
      print('${(i + 1).toString().padLeft(3)} - ${_taskNames[i]}');
    }
    exitCode = 0;
    return;
  }

52
  if (_taskNames.isEmpty) {
53 54
    stderr.writeln('Failed to find tasks to run based on supplied options.');
    exitCode = 1;
55
    return;
56 57
  }

58 59 60
  final bool silent = args['silent'] as bool;
  final String localEngine = args['local-engine'] as String;
  final String localEngineSrcPath = args['local-engine-src-path'] as String;
61

62
  for (final String taskName in _taskNames) {
63
    section('Running task "$taskName"');
64 65 66 67 68 69
    final Map<String, dynamic> result = await runTask(
      taskName,
      silent: silent,
      localEngine: localEngine,
      localEngineSrcPath: localEngineSrcPath,
    );
70 71

    print('Task result:');
72
    print(const JsonEncoder.withIndent('  ').convert(result));
73
    section('Finished task "$taskName"');
74

75
    if (!(result['success'] as bool)) {
76
      exitCode = 1;
77
      if (args['exit'] as bool) {
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
        return;
      }
    }
  }
}

void addTasks({
  List<ManifestTask> tasks,
  ArgResults args,
  List<String> taskNames,
}) {
  if (args.wasParsed('continue-from')) {
    final int index = tasks.indexWhere((ManifestTask task) => task.name == args['continue-from']);
    if (index == -1) {
      throw Exception('Invalid task name "${args['continue-from']}"');
    }
    tasks.removeRange(0, index);
  }
  // Only start skipping if user specified a task to continue from
97
  final String stage = args['stage'] as String;
98
  for (final ManifestTask task in tasks) {
99
    final bool isQualifyingStage = stage == null || task.stage == stage;
100
    final bool isQualifyingHost = !(args['match-host-platform'] as bool) || task.isSupportedByHost();
101 102 103
    if (isQualifyingHost && isQualifyingStage) {
      taskNames.add(task.name);
    }
104 105 106 107
  }
}

/// Command-line options for the `run.dart` command.
108
final ArgParser _argParser = ArgParser()
109
  ..addMultiOption(
110 111 112
    'task',
    abbr: 't',
    splitCommas: true,
113 114 115 116 117 118
    help: 'Either:\n'
        ' - the name of a task defined in manifest.yaml. Example: complex_layout__start_up.\n'
        ' - the path to a Dart file corresponding to a task, which resides in bin/tasks. Example: bin/tasks/complex_layout__start_up.dart.\n'
        '\n'
        'This option may be repeated to specify multiple tasks.',
    callback: (List<String> value) {
119
      for (final String nameOrPath in value) {
120 121
        final List<String> fragments = path.split(nameOrPath);
        final bool isDartFile = fragments.last.endsWith('.dart');
122 123 124 125

        if (fragments.length == 1 && !isDartFile) {
          // Not a path
          _taskNames.add(nameOrPath);
126
        } else if (!isDartFile || !path.equals(path.dirname(nameOrPath), path.join('bin', 'tasks'))) {
127
          // Unsupported executable location
128
          throw FormatException('Invalid value for option -t (--task): $nameOrPath');
129 130 131 132 133
        } else {
          _taskNames.add(path.withoutExtension(fragments.last));
        }
      }
    },
134
  )
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
  ..addFlag(
    'all',
    abbr: 'a',
    help: 'Runs all tasks defined in manifest.yaml in alphabetical order.',
  )
  ..addOption(
    'continue-from',
    abbr: 'c',
    help: 'With --all or --stage, continue from the given test.',
  )
  ..addFlag(
    'exit',
    defaultsTo: true,
    help: 'Exit on the first test failure.',
  )
  ..addOption(
    'local-engine',
    help: 'Name of a build output within the engine out directory, if you\n'
          'are building Flutter locally. Use this to select a specific\n'
          'version of the engine if you have built multiple engine targets.\n'
          'This path is relative to --local-engine-src-path/out.',
  )
  ..addFlag(
    'list',
    abbr: 'l',
    help: 'Don\'t actually run the tasks, but list out the tasks that would\n'
          'have been run, in the order they would have run.',
  )
  ..addOption(
    'local-engine-src-path',
    help: 'Path to your engine src directory, if you are building Flutter\n'
          'locally. Defaults to \$FLUTTER_ENGINE if set, or tries to guess at\n'
          'the location based on the value of the --flutter-root option.',
  )
  ..addFlag(
    'match-host-platform',
    defaultsTo: true,
    help: 'Only run tests that match the host platform (e.g. do not run a\n'
          'test with a `required_agent_capabilities` value of "mac/android"\n'
174
          'on a windows host). Each test publishes its '
175 176
          '`required_agent_capabilities`\nin the `manifest.yaml` file.',
  )
177 178 179
  ..addOption(
    'stage',
    abbr: 's',
180 181
    help: 'Name of the stage. Runs all tasks for that stage. The tasks and\n'
          'their stages are read from manifest.yaml.',
182
  )
183
  ..addFlag(
184 185 186
    'silent',
    negatable: true,
    defaultsTo: false,
187
  )
188
  ..addMultiOption(
189 190 191 192 193
    'test',
    hide: true,
    splitCommas: true,
    callback: (List<String> value) {
      if (value.isNotEmpty) {
194
        throw const FormatException(
195 196 197 198 199
          'Invalid option --test. Did you mean --task (-t)?',
        );
      }
    },
  );