Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1eb8a34

Browse files
authored
Android lint to NNBD (#26826)
1 parent 759341e commit 1eb8a34

File tree

3 files changed

+223
-235
lines changed

3 files changed

+223
-235
lines changed

tools/android_lint/bin/main.dart

Lines changed: 222 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,227 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
// @dart=2.9
5+
import 'dart:io';
66

7-
import 'sound_main.dart' as m;
7+
import 'package:args/args.dart';
8+
import 'package:path/path.dart' as path;
9+
import 'package:process/process.dart';
810

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

0 commit comments

Comments
 (0)