blob: f9a9e4359dc28883ace82fbccb049ca6a1617b74 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. 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:path/path.dart' as path;
import 'package:process/process.dart';
const LocalProcessManager processManager = LocalProcessManager();
/// Runs the Android SDK Lint tool on flutter/shell/platform/android.
///
/// This script scans the flutter/shell/platform/android directory for Java
/// files to build a `project.xml` file. This file is then passed to the lint
/// tool. If an `--html` flag is also passed in, HTML output is reqeusted in the
/// directory for the optional `--out` parameter, which defaults to
/// `lint_report`. Otherwise the output is printed to STDOUT.
///
/// The `--in` parameter may be specified to force this script to scan a
/// specific location for the engine repository, and expects to be given the
/// `src` directory that contains both `third_party` and `flutter`.
///
/// At the time of this writing, the Android Lint tool doesn't work well with
/// Java > 1.8. This script will print a warning if you are not running
/// Java 1.8.
Future<void> main(List<String> args) async {
final ArgParser argParser = setupOptions();
await checkJava1_8();
final int exitCode = await runLint(argParser, argParser.parse(args));
exit(exitCode);
}
Future<int> runLint(ArgParser argParser, ArgResults argResults) async {
final Directory androidDir = Directory(path.join(
argResults['in'],
'flutter',
'shell',
'platform',
'android',
));
if (!androidDir.existsSync()) {
print('This command must be run from the engine/src directory, '
'or be passed that directory as the --in parameter.\n');
print(argParser.usage);
return -1;
}
final Directory androidSdkDir = Directory(
path.join(argResults['in'], 'third_party', 'android_tools', 'sdk'),
);
if (!androidSdkDir.existsSync()) {
print('The Android SDK for this engine is missing from the '
'third_party/android_tools directory. Have you run gclient sync?\n');
print(argParser.usage);
return -1;
}
if (argResults['rebaseline']) {
print('Removing previous baseline.xml...');
final File baselineXml = File(baselineXmlPath);
if (baselineXml.existsSync()) {
await baselineXml.delete();
}
}
print('Preparing projext.xml...');
final IOSink projectXml = File(projectXmlPath).openWrite();
projectXml.write(
'''<!-- THIS FILE IS GENERATED. PLEASE USE THE INCLUDED DART PROGRAM WHICH -->
<!-- WILL AUTOMATICALLY FIND ALL .java FILES AND INCLUDE THEM HERE -->
<project>
<sdk dir="${androidSdkDir.path}" />
<module name="FlutterEngine" android="true" library="true" compile-sdk-version="android-P">
<manifest file="${path.join(androidDir.path, 'AndroidManifest.xml')}" />
''');
for (final FileSystemEntity entity in androidDir.listSync(recursive: true)) {
if (!entity.path.endsWith('.java')) {
continue;
}
projectXml.writeln(' <src file="${entity.path}" />');
}
projectXml.write(''' </module>
</project>
''');
await projectXml.close();
print('Wrote project.xml, starting lint...');
final List<String> lintArgs = <String>[
path.join(androidSdkDir.path, 'tools', 'bin', 'lint'),
'--project',
projectXmlPath,
'--showall',
'--exitcode', // Set non-zero exit code on errors
'-Wall',
'-Werror',
'--baseline',
baselineXmlPath,
];
if (argResults['html']) {
lintArgs.addAll(<String>['--html', argResults['out']]);
}
final String javaHome = await getJavaHome();
final Process lintProcess = await processManager.start(
lintArgs,
environment: javaHome != null
? <String, String>{
'JAVA_HOME': javaHome,
}
: null,
);
lintProcess.stdout.pipe(stdout);
lintProcess.stderr.pipe(stderr);
return await lintProcess.exitCode;
}
/// Prepares an [ArgParser] for this script.
ArgParser setupOptions() {
final ArgParser argParser = ArgParser();
argParser
..addOption(
'in',
help: 'The path to `engine/src`.',
defaultsTo: path.relative(
path.join(
projectDir,
'..',
'..',
'..',
),
),
)
..addFlag(
'help',
help: 'Print usage of the command.',
negatable: false,
defaultsTo: false,
)
..addFlag(
'rebaseline',
help: 'Recalculates the baseline for errors and warnings '
'in this project.',
negatable: false,
defaultsTo: false,
)
..addFlag(
'html',
help: 'Creates an HTML output for this report instead of printing '
'command line output.',
negatable: false,
defaultsTo: false,
)
..addOption(
'out',
help: 'The path to write the generated the HTML report to. Ignored if '
'--html is not also true.',
defaultsTo: path.join(projectDir, 'lint_report'),
);
return argParser;
}
/// On macOS, we can try to find Java 1.8.
///
/// Otherwise, default to whatever JAVA_HOME is already.
Future<String> getJavaHome() async {
if (Platform.isMacOS) {
final ProcessResult result = await processManager.run(
<String>['/usr/libexec/java_home', '-v', '1.8', '-F'],
);
if (result.exitCode == 0) {
return result.stdout.trim();
}
}
return Platform.environment['JAVA_HOME'];
}
/// Checks that `java` points to Java 1.8.
///
/// The SDK lint tool may not work with Java > 1.8.
Future<void> checkJava1_8() async {
print('Checking Java version...');
if (Platform.isMacOS) {
final ProcessResult result = await processManager.run(
<String>['/usr/libexec/java_home', '-v', '1.8', '-F'],
);
if (result.exitCode != 0) {
print('Java 1.8 not available - the linter may not work properly.');
}
return;
}
final ProcessResult javaResult = await processManager.run(
<String>['java', '-version'],
);
if (javaResult.exitCode != 0) {
print('Could not run "java -version". '
'Ensure Java is installed and available on your path.');
print(javaResult.stderr);
}
// `java -version` writes to stderr.
final String javaVersionStdout = javaResult.stderr;
if (!javaVersionStdout.contains('"1.8')) {
print('The Android SDK tools may not work properly with your Java version. '
'If this process fails, please retry using Java 1.8.');
}
}
/// The root directory of this project.
String get projectDir => path.dirname(
path.dirname(
path.fromUri(Platform.script),
),
);
/// The path to use for project.xml, which tells the linter where to find source
/// files.
String get projectXmlPath => path.join(projectDir, 'project.xml');
/// The path to use for baseline.xml, which tells the linter what errors or
/// warnings to ignore.
String get baselineXmlPath => path.join(projectDir, 'baseline.xml');