| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:io'; |
| |
| import 'package:args/args.dart'; |
| import 'package:coverage/coverage.dart'; |
| import 'package:path/path.dart' as p; |
| |
| /// [Environment] stores gathered arguments information. |
| class Environment { |
| String sdkRoot; |
| String pkgRoot; |
| String packagesPath; |
| String baseDirectory; |
| String input; |
| IOSink output; |
| List<String> reportOn; |
| String bazelWorkspace; |
| bool bazel; |
| int workers; |
| bool prettyPrint; |
| bool lcov; |
| bool expectMarkers; |
| bool checkIgnore; |
| bool verbose; |
| } |
| |
| Future<Null> main(List<String> arguments) async { |
| final env = parseArgs(arguments); |
| |
| final files = filesToProcess(env.input); |
| if (env.verbose) { |
| print('Environment:'); |
| print(' # files: ${files.length}'); |
| print(' # workers: ${env.workers}'); |
| print(' sdk-root: ${env.sdkRoot}'); |
| print(' package-root: ${env.pkgRoot}'); |
| print(' package-spec: ${env.packagesPath}'); |
| print(' report-on: ${env.reportOn}'); |
| print(' check-ignore: ${env.checkIgnore}'); |
| } |
| |
| final clock = Stopwatch()..start(); |
| final hitmap = await parseCoverage( |
| files, |
| env.workers, |
| checkIgnoredLines: env.checkIgnore, |
| ); |
| |
| // All workers are done. Process the data. |
| if (env.verbose) { |
| print('Done creating global hitmap. Took ${clock.elapsedMilliseconds} ms.'); |
| } |
| |
| String output; |
| final resolver = env.bazel |
| ? BazelResolver(workspacePath: env.bazelWorkspace) |
| : Resolver( |
| packagesPath: env.packagesPath, |
| // ignore_for_file: deprecated_member_use_from_same_package |
| packageRoot: env.pkgRoot, |
| sdkRoot: env.sdkRoot); |
| final loader = Loader(); |
| if (env.prettyPrint) { |
| output = |
| await PrettyPrintFormatter(resolver, loader, reportOn: env.reportOn) |
| .format(hitmap); |
| } else { |
| assert(env.lcov); |
| output = await LcovFormatter(resolver, |
| reportOn: env.reportOn, basePath: env.baseDirectory) |
| .format(hitmap); |
| } |
| |
| env.output.write(output); |
| await env.output.flush(); |
| if (env.verbose) { |
| print('Done flushing output. Took ${clock.elapsedMilliseconds} ms.'); |
| } |
| |
| if (env.verbose) { |
| if (resolver.failed.isNotEmpty) { |
| print('Failed to resolve:'); |
| for (var error in resolver.failed.toSet()) { |
| print(' $error'); |
| } |
| } |
| if (loader.failed.isNotEmpty) { |
| print('Failed to load:'); |
| for (var error in loader.failed.toSet()) { |
| print(' $error'); |
| } |
| } |
| } |
| await env.output.close(); |
| } |
| |
| /// Checks the validity of the provided arguments. Does not initialize actual |
| /// processing. |
| Environment parseArgs(List<String> arguments) { |
| final env = Environment(); |
| final parser = ArgParser(); |
| |
| parser.addOption('sdk-root', abbr: 's', help: 'path to the SDK root'); |
| parser.addOption('package-root', abbr: 'p', help: 'path to the package root'); |
| parser.addOption('packages', help: 'path to the package spec file'); |
| parser.addOption('in', abbr: 'i', help: 'input(s): may be file or directory'); |
| parser.addOption('out', |
| abbr: 'o', defaultsTo: 'stdout', help: 'output: may be file or stdout'); |
| parser.addMultiOption('report-on', |
| help: 'which directories or files to report coverage on'); |
| parser.addOption('workers', |
| abbr: 'j', defaultsTo: '1', help: 'number of workers'); |
| parser.addOption('bazel-workspace', |
| defaultsTo: '', help: 'Bazel workspace directory'); |
| parser.addOption('base-directory', |
| abbr: 'b', |
| help: 'the base directory relative to which source paths are output'); |
| parser.addFlag('bazel', |
| defaultsTo: false, help: 'use Bazel-style path resolution'); |
| parser.addFlag('pretty-print', |
| abbr: 'r', |
| negatable: false, |
| help: 'convert coverage data to pretty print format'); |
| parser.addFlag('lcov', |
| abbr: 'l', |
| negatable: false, |
| help: 'convert coverage data to lcov format'); |
| parser.addFlag('verbose', |
| abbr: 'v', negatable: false, help: 'verbose output'); |
| parser.addFlag( |
| 'check-ignore', |
| abbr: 'c', |
| negatable: false, |
| help: 'check for coverage ignore comments.' |
| ' Not supported in web coverage.', |
| ); |
| parser.addFlag('help', abbr: 'h', negatable: false, help: 'show this help'); |
| |
| final args = parser.parse(arguments); |
| |
| void printUsage() { |
| print('Usage: dart format_coverage.dart [OPTION...]\n'); |
| print(parser.usage); |
| } |
| |
| void fail(String msg) { |
| print('\n$msg\n'); |
| printUsage(); |
| exit(1); |
| } |
| |
| if (args['help'] as bool) { |
| printUsage(); |
| exit(0); |
| } |
| |
| env.sdkRoot = args['sdk-root'] as String; |
| if (env.sdkRoot != null) { |
| env.sdkRoot = p.normalize(p.join(p.absolute(env.sdkRoot), 'lib')); |
| if (!FileSystemEntity.isDirectorySync(env.sdkRoot)) { |
| fail('Provided SDK root "${args["sdk-root"]}" is not a valid SDK ' |
| 'top-level directory'); |
| } |
| } |
| |
| if (args['package-root'] != null && args['packages'] != null) { |
| fail('Only one of --package-root or --packages may be specified.'); |
| } |
| |
| env.packagesPath = args['packages'] as String; |
| if (env.packagesPath != null) { |
| if (!FileSystemEntity.isFileSync(env.packagesPath)) { |
| fail('Package spec "${args["packages"]}" not found, or not a file.'); |
| } |
| } |
| |
| env.pkgRoot = args['package-root'] as String; |
| if (env.pkgRoot != null) { |
| env.pkgRoot = p.absolute(p.normalize(args['package-root'] as String)); |
| if (!FileSystemEntity.isDirectorySync(env.pkgRoot)) { |
| fail('Package root "${args["package-root"]}" is not a directory.'); |
| } |
| } |
| |
| if (args['in'] == null) fail('No input files given.'); |
| env.input = p.absolute(p.normalize(args['in'] as String)); |
| if (!FileSystemEntity.isDirectorySync(env.input) && |
| !FileSystemEntity.isFileSync(env.input)) { |
| fail('Provided input "${args["in"]}" is neither a directory nor a file.'); |
| } |
| |
| if (args['out'] == 'stdout') { |
| env.output = stdout; |
| } else { |
| final outpath = p.absolute(p.normalize(args['out'] as String)); |
| final outfile = File(outpath)..createSync(recursive: true); |
| env.output = outfile.openWrite(); |
| } |
| |
| final reportOn = args['report-on'] as List<String>; |
| env.reportOn = reportOn.isNotEmpty ? reportOn : null; |
| |
| env.bazel = args['bazel'] as bool; |
| env.bazelWorkspace = args['bazel-workspace'] as String; |
| if (env.bazelWorkspace.isNotEmpty && !env.bazel) { |
| stderr.writeln('warning: ignoring --bazel-workspace: --bazel not set'); |
| } |
| |
| if (args['base-directory'] != null) { |
| env.baseDirectory = p.absolute(args['base-directory'] as String); |
| } |
| |
| env.lcov = args['lcov'] as bool; |
| if (args['pretty-print'] as bool && env.lcov) { |
| fail('Choose one of pretty-print or lcov output'); |
| } |
| // Use pretty-print either explicitly or by default. |
| env.prettyPrint = !env.lcov; |
| |
| try { |
| env.workers = int.parse('${args["workers"]}'); |
| } catch (e) { |
| fail('Invalid worker count: $e'); |
| } |
| |
| env.checkIgnore = args['check-ignore'] as bool; |
| env.verbose = args['verbose'] as bool; |
| return env; |
| } |
| |
| /// Given an absolute path absPath, this function returns a [List] of files |
| /// are contained by it if it is a directory, or a [List] containing the file if |
| /// it is a file. |
| List<File> filesToProcess(String absPath) { |
| if (FileSystemEntity.isDirectorySync(absPath)) { |
| return Directory(absPath) |
| .listSync(recursive: true) |
| .whereType<File>() |
| .where((e) => e.path.endsWith('.json')) |
| .toList(); |
| } |
| return <File>[File(absPath)]; |
| } |