• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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