| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // @dart = 2.8 |
| |
| // TODO(https://fxbug.dev/84961): Fix null safety and remove this language version. |
| // @dart=2.9 |
| |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:args/args.dart'; |
| import 'package:collection/collection.dart'; |
| import 'package:dart_strict_deps_lib/dependency_check.dart' as dependency_check; |
| import 'package:dart_strict_deps_lib/file_processor.dart' as file_processor; |
| import 'package:meta/meta.dart'; |
| import 'package:package_config/package_config.dart'; |
| import 'package:path/path.dart' as p; |
| import 'package:tools.dart-strict-deps.dart_strict_deps_proto/protos/models.pb.dart'; |
| |
| p.Context context = p.Context(style: p.Style.posix); |
| |
| /// Writes serialized TargetCheckResult to file |
| void writeOutfile(String file, TargetCheckResult checkResult) { |
| File(file).writeAsString(jsonEncode(checkResult.toProto3Json())); |
| } |
| |
| // Returns string [s] with [indents] number of spaces in front. |
| String indentedString(String s, {@required int indents}) { |
| return s.padLeft(s.length + indents, ' '); |
| } |
| |
| /// Represents a flattened out structure for the parsed data. |
| /// |
| /// This is used for easier formatting of output. |
| class FlatResult { |
| // File path of the importing file |
| final String filePath; |
| // Import statement being represented |
| final ImportResult importResult; |
| // Whether the package of import has been imported or not |
| final bool knownPackage; |
| FlatResult(this.filePath, this.importResult, {this.knownPackage}); |
| } |
| |
| /// Prints file level warnings to stdout. |
| void printFileWarnings(String fileName, List<FlatResult> flatResults) { |
| final fileState = flatResults[0].importResult.state; |
| if (fileState == ImportResult_State.MISSING_FROM_PACKAGE_SOURCES) { |
| print(indentedString( |
| 'File `$fileName` was not found in package sources list. Used here:', |
| indents: 2)); |
| } else if (fileState == ImportResult_State.FILE_MISSING) { |
| print(indentedString( |
| 'File `$fileName` does not exist on the filesystem. Used here:', |
| indents: 2)); |
| } else { |
| print(indentedString('File `$fileName` could not be found. Used here:', |
| indents: 2)); |
| } |
| for (var importer in flatResults) { |
| print(indentedString( |
| 'Line ${importer.importResult.lineInfo}: ${importer.filePath}', |
| indents: 4)); |
| } |
| } |
| |
| /// Prints package level warnings to stdout. |
| void printPackageWarnings(MapEntry packageResult) { |
| Map<String, List<FlatResult>> byFile = groupBy(packageResult.value, |
| (packageResult) => packageResult.importResult.resolvedLocation); |
| if (packageResult.value[0].importResult.isDartPackageImported) { |
| print( |
| 'Package ${packageResult.key} imported directly and errors found with files imported'); |
| } else if (packageResult.value[0].knownPackage) { |
| print( |
| 'Package ${packageResult.key} imported transitively and errors found with files imported from it:'); |
| } else { |
| print( |
| 'Package ${packageResult.key} was not a GN dep but files were imported from it:'); |
| } |
| byFile.forEach(printFileWarnings); |
| } |
| |
| /// Finds and prints warnings to stdout. |
| void printWarnings( |
| {TargetCheckResult targetCheckResult, |
| String outFile, |
| PackageConfig resolver}) { |
| final knownPackages = |
| resolver.packages.map((package) => package.name).toList(); |
| |
| final flatResults = targetCheckResult.files.expand((file) => file.imports.map( |
| (importResult) => FlatResult(file.filePath, importResult, |
| knownPackage: knownPackages.contains(importResult.dartPackage)))); |
| |
| final notFoundResults = flatResults |
| .where((result) => result.importResult.state != ImportResult_State.FOUND); |
| |
| final notFoundByPackage = |
| groupBy(notFoundResults, (result) => result.importResult.dartPackage); |
| |
| notFoundByPackage.entries.forEach(printPackageWarnings); |
| if (notFoundByPackage.entries.isNotEmpty) { |
| exitCode = -1; |
| } |
| } |
| |
| void main(List<String> args) async { |
| var parser = ArgParser() |
| ..addOption('metadata-file', help: 'collected metadata file from GN') |
| ..addOption('output-file', help: 'output file to write result to') |
| ..addOption('packages-file', |
| help: 'packages file to read dependency info from'); |
| var parsedArgs = parser.parse(args); |
| String inputFile = parsedArgs['metadata-file']; |
| String outFile = parsedArgs['output-file']; |
| String packagesFile = parsedArgs['packages-file']; |
| PackageConfig resolver = await loadPackageConfig(File(packagesFile).absolute); |
| String inputFileContents = File(inputFile).readAsStringSync(); |
| final targetCheckResult = dependency_check.checkBuildInfo( |
| buildInfo: file_processor.buildInfoFromJson(inputFileContents), |
| resolver: resolver); |
| printWarnings( |
| targetCheckResult: targetCheckResult, |
| outFile: outFile, |
| resolver: resolver); |
| writeOutfile(outFile, targetCheckResult); |
| } |