1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:io'; 6 7import 'package:args/args.dart'; 8import 'package:path/path.dart' as path; 9import 'package:process/process.dart'; 10 11const LocalProcessManager processManager = LocalProcessManager(); 12 13/// Runs the Android SDK Lint tool on flutter/shell/platform/android. 14/// 15/// This script scans the flutter/shell/platform/android directory for Java 16/// files to build a `project.xml` file. This file is then passed to the lint 17/// tool. If an `--html` flag is also passed in, HTML output is reqeusted in the 18/// directory for the optional `--out` parameter, which defaults to 19/// `lint_report`. Otherwise the output is printed to STDOUT. 20/// 21/// The `--in` parameter may be specified to force this script to scan a 22/// specific location for the engine repository, and expects to be given the 23/// `src` directory that contains both `third_party` and `flutter`. 24/// 25/// At the time of this writing, the Android Lint tool doesn't work well with 26/// Java > 1.8. This script will print a warning if you are not running 27/// Java 1.8. 28Future<void> main(List<String> args) async { 29 final ArgParser argParser = setupOptions(); 30 await checkJava1_8(); 31 final int exitCode = await runLint(argParser, argParser.parse(args)); 32 exit(exitCode); 33} 34 35Future<int> runLint(ArgParser argParser, ArgResults argResults) async { 36 final Directory androidDir = Directory(path.join( 37 argResults['in'], 38 'flutter', 39 'shell', 40 'platform', 41 'android', 42 )); 43 if (!androidDir.existsSync()) { 44 print('This command must be run from the engine/src directory, ' 45 'or be passed that directory as the --in parameter.\n'); 46 print(argParser.usage); 47 return -1; 48 } 49 50 final Directory androidSdkDir = Directory( 51 path.join(argResults['in'], 'third_party', 'android_tools', 'sdk'), 52 ); 53 54 if (!androidSdkDir.existsSync()) { 55 print('The Android SDK for this engine is missing from the ' 56 'third_party/android_tools directory. Have you run gclient sync?\n'); 57 print(argParser.usage); 58 return -1; 59 } 60 61 if (argResults['rebaseline']) { 62 print('Removing previous baseline.xml...'); 63 final File baselineXml = File(baselineXmlPath); 64 if (baselineXml.existsSync()) { 65 await baselineXml.delete(); 66 } 67 } 68 print('Preparing projext.xml...'); 69 final IOSink projectXml = File(projectXmlPath).openWrite(); 70 projectXml.write( 71 '''<!-- THIS FILE IS GENERATED. PLEASE USE THE INCLUDED DART PROGRAM WHICH --> 72<!-- WILL AUTOMATICALLY FIND ALL .java FILES AND INCLUDE THEM HERE --> 73<project> 74 <sdk dir="${androidSdkDir.path}" /> 75 <module name="FlutterEngine" android="true" library="true" compile-sdk-version="android-P"> 76 <manifest file="${path.join(androidDir.path, 'AndroidManifest.xml')}" /> 77'''); 78 for (final FileSystemEntity entity in androidDir.listSync(recursive: true)) { 79 if (!entity.path.endsWith('.java')) { 80 continue; 81 } 82 projectXml.writeln(' <src file="${entity.path}" />'); 83 } 84 85 projectXml.write(''' </module> 86</project> 87'''); 88 await projectXml.close(); 89 90 print('Wrote project.xml, starting lint...'); 91 final List<String> lintArgs = <String>[ 92 path.join(androidSdkDir.path, 'tools', 'bin', 'lint'), 93 '--project', 94 projectXmlPath, 95 '--showall', 96 '--exitcode', // Set non-zero exit code on errors 97 '-Wall', 98 '-Werror', 99 '--baseline', 100 baselineXmlPath, 101 ]; 102 if (argResults['html']) { 103 lintArgs.addAll(<String>['--html', argResults['out']]); 104 } 105 final String javaHome = await getJavaHome(); 106 final Process lintProcess = await processManager.start( 107 lintArgs, 108 environment: javaHome != null 109 ? <String, String>{ 110 'JAVA_HOME': javaHome, 111 } 112 : null, 113 ); 114 lintProcess.stdout.pipe(stdout); 115 lintProcess.stderr.pipe(stderr); 116 return await lintProcess.exitCode; 117} 118 119/// Prepares an [ArgParser] for this script. 120ArgParser setupOptions() { 121 final ArgParser argParser = ArgParser(); 122 argParser 123 ..addOption( 124 'in', 125 help: 'The path to `engine/src`.', 126 defaultsTo: path.relative( 127 path.join( 128 projectDir, 129 '..', 130 '..', 131 '..', 132 ), 133 ), 134 ) 135 ..addFlag( 136 'help', 137 help: 'Print usage of the command.', 138 negatable: false, 139 defaultsTo: false, 140 ) 141 ..addFlag( 142 'rebaseline', 143 help: 'Recalculates the baseline for errors and warnings ' 144 'in this project.', 145 negatable: false, 146 defaultsTo: false, 147 ) 148 ..addFlag( 149 'html', 150 help: 'Creates an HTML output for this report instead of printing ' 151 'command line output.', 152 negatable: false, 153 defaultsTo: false, 154 ) 155 ..addOption( 156 'out', 157 help: 'The path to write the generated the HTML report to. Ignored if ' 158 '--html is not also true.', 159 defaultsTo: path.join(projectDir, 'lint_report'), 160 ); 161 162 return argParser; 163} 164 165/// On macOS, we can try to find Java 1.8. 166/// 167/// Otherwise, default to whatever JAVA_HOME is already. 168Future<String> getJavaHome() async { 169 if (Platform.isMacOS) { 170 final ProcessResult result = await processManager.run( 171 <String>['/usr/libexec/java_home', '-v', '1.8', '-F'], 172 ); 173 if (result.exitCode == 0) { 174 return result.stdout.trim(); 175 } 176 } 177 return Platform.environment['JAVA_HOME']; 178} 179 180/// Checks that `java` points to Java 1.8. 181/// 182/// The SDK lint tool may not work with Java > 1.8. 183Future<void> checkJava1_8() async { 184 print('Checking Java version...'); 185 186 if (Platform.isMacOS) { 187 final ProcessResult result = await processManager.run( 188 <String>['/usr/libexec/java_home', '-v', '1.8', '-F'], 189 ); 190 if (result.exitCode != 0) { 191 print('Java 1.8 not available - the linter may not work properly.'); 192 } 193 return; 194 } 195 final ProcessResult javaResult = await processManager.run( 196 <String>['java', '-version'], 197 ); 198 if (javaResult.exitCode != 0) { 199 print('Could not run "java -version". ' 200 'Ensure Java is installed and available on your path.'); 201 print(javaResult.stderr); 202 } 203 // `java -version` writes to stderr. 204 final String javaVersionStdout = javaResult.stderr; 205 if (!javaVersionStdout.contains('"1.8')) { 206 print('The Android SDK tools may not work properly with your Java version. ' 207 'If this process fails, please retry using Java 1.8.'); 208 } 209} 210 211/// The root directory of this project. 212String get projectDir => path.dirname( 213 path.dirname( 214 path.fromUri(Platform.script), 215 ), 216 ); 217 218/// The path to use for project.xml, which tells the linter where to find source 219/// files. 220String get projectXmlPath => path.join(projectDir, 'project.xml'); 221 222/// The path to use for baseline.xml, which tells the linter what errors or 223/// warnings to ignore. 224String get baselineXmlPath => path.join(projectDir, 'baseline.xml'); 225