Add android analyzer commands for applinks (#131009)
Since applink validation tool is going to be a static tool, It won't have access to vmservices.
[flutter.dev/go/static-tooling-in-devtools](http://flutter.dev/go/static-tooling-in-devtools)
I remove the vm services and also update the deeplink task to also include path pattern and custom scheme
http://go/android-applink-apis (internal only)
diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart
index 2c1c637..dff347f 100644
--- a/packages/flutter_tools/lib/src/commands/analyze.dart
+++ b/packages/flutter_tools/lib/src/commands/analyze.dart
@@ -16,6 +16,7 @@
import 'analyze_base.dart';
import 'analyze_continuously.dart';
import 'analyze_once.dart';
+import 'android_analyze.dart';
import 'validate_project.dart';
class AnalyzeCommand extends FlutterCommand {
@@ -99,6 +100,37 @@
argParser.addFlag('fatal-warnings',
help: 'Treat warning level issues as fatal.',
defaultsTo: true);
+
+ argParser.addFlag('android',
+ negatable: false,
+ help: 'Analyze Android sub-project. Used by internal tools only.',
+ hide: !verboseHelp,
+ );
+
+ if (verboseHelp) {
+ argParser.addSeparator('Usage: flutter analyze --android [arguments]');
+ }
+
+ argParser.addFlag('list-build-variants',
+ negatable: false,
+ help: 'Print out a list of available build variants for the '
+ 'Android sub-project.',
+ hide: !verboseHelp,
+ );
+
+ argParser.addFlag('output-app-link-settings',
+ negatable: false,
+ help: 'Output a JSON with Android app link settings into a file. '
+ 'The "--build-variant" must also be set.',
+ hide: !verboseHelp,
+ );
+
+ argParser.addOption('build-variant',
+ help: 'Sets the Android build variant to be analyzed.',
+ valueHelp: 'use "flutter analyze --android --list-build-variants" to get '
+ 'all available build variants',
+ hide: !verboseHelp,
+ );
}
/// The working directory for testing analysis using dartanalyzer.
@@ -142,12 +174,51 @@
return false;
}
+ // Don't run pub if asking for android analysis.
+ if (boolArg('android')) {
+ return false;
+ }
+
return super.shouldRunPub;
}
@override
Future<FlutterCommandResult> runCommand() async {
- if (boolArg('suggestions')) {
+ if (boolArg('android')) {
+ final AndroidAnalyzeOption option;
+ final String? buildVariant;
+ if (argResults!['list-build-variants'] as bool && argResults!['output-app-link-settings'] as bool) {
+ throwToolExit('Only one of "--list-build-variants" or "--output-app-link-settings" can be provided');
+ }
+ if (argResults!['list-build-variants'] as bool) {
+ option = AndroidAnalyzeOption.listBuildVariant;
+ buildVariant = null;
+ } else if (argResults!['output-app-link-settings'] as bool) {
+ option = AndroidAnalyzeOption.outputAppLinkSettings;
+ buildVariant = argResults!['build-variant'] as String?;
+ if (buildVariant == null) {
+ throwToolExit('"--build-variant" must be provided');
+ }
+ } else {
+ throwToolExit('No argument is provided to analyze. Use -h to see available commands.');
+ }
+ final Set<String> items = findDirectories(argResults!, _fileSystem);
+ final String directoryPath;
+ if (items.isEmpty) { // user did not specify any path
+ directoryPath = _fileSystem.currentDirectory.path;
+ } else if (items.length > 1) { // if the user sends more than one path
+ throwToolExit('The Android analyze can process only one directory path');
+ } else {
+ directoryPath = items.first;
+ }
+ await AndroidAnalyze(
+ fileSystem: _fileSystem,
+ option: option,
+ userPath: directoryPath,
+ buildVariant: buildVariant,
+ logger: _logger,
+ ).analyze();
+ } else if (boolArg('suggestions')) {
final String directoryPath;
if (boolArg('watch')) {
throwToolExit('flag --watch is not compatible with --suggestions');
diff --git a/packages/flutter_tools/lib/src/commands/android_analyze.dart b/packages/flutter_tools/lib/src/commands/android_analyze.dart
new file mode 100644
index 0000000..c4389ff
--- /dev/null
+++ b/packages/flutter_tools/lib/src/commands/android_analyze.dart
@@ -0,0 +1,56 @@
+// Copyright 2014 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:async';
+
+import '../base/file_system.dart';
+import '../base/logger.dart';
+import '../convert.dart';
+import '../project.dart';
+
+/// The type of analysis to perform.
+enum AndroidAnalyzeOption {
+ /// Prints out available build variants of the Android sub-project.
+ ///
+ /// An example output:
+ /// ["debug", "profile", "release"]
+ listBuildVariant,
+
+ /// Outputs app link settings of the Android sub-project into a file.
+ ///
+ /// The file path will be printed after the command is run successfully.
+ outputAppLinkSettings,
+}
+
+/// Analyze the Android sub-project of a Flutter project.
+///
+/// The [userPath] must be point to a flutter project.
+class AndroidAnalyze {
+ AndroidAnalyze({
+ required this.fileSystem,
+ required this.option,
+ required this.userPath,
+ this.buildVariant,
+ required this.logger,
+ }) : assert(option == AndroidAnalyzeOption.listBuildVariant || buildVariant != null);
+
+ final FileSystem fileSystem;
+ final AndroidAnalyzeOption option;
+ final String? buildVariant;
+ final String userPath;
+ final Logger logger;
+
+ Future<void> analyze() async {
+ final FlutterProject project = FlutterProject.fromDirectory(fileSystem.directory(userPath));
+ switch (option) {
+ case AndroidAnalyzeOption.listBuildVariant:
+ logger.printStatus(jsonEncode(await project.android.getBuildVariants()));
+ case AndroidAnalyzeOption.outputAppLinkSettings:
+ assert(buildVariant != null);
+ await project.android.outputsAppLinkSettings(variant: buildVariant!);
+ final String filePath = fileSystem.path.join(project.directory.path, 'build', 'app', 'app-link-settings-$buildVariant.json`');
+ logger.printStatus('result saved in $filePath');
+ }
+ }
+}
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/android_analyze_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/android_analyze_test.dart
new file mode 100644
index 0000000..44c9c5f
--- /dev/null
+++ b/packages/flutter_tools/test/commands.shard/hermetic/android_analyze_test.dart
@@ -0,0 +1,129 @@
+// Copyright 2014 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:async';
+
+import 'package:args/command_runner.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/android/android_builder.dart';
+import 'package:flutter_tools/src/artifacts.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/analyze.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:flutter_tools/src/project_validator.dart';
+import 'package:test/fake.dart';
+
+import '../../src/context.dart';
+import '../../src/test_flutter_command_runner.dart';
+
+void main() {
+
+ group('Android analyze command', () {
+ late FileSystem fileSystem;
+ late Platform platform;
+ late BufferLogger logger;
+ late FakeProcessManager processManager;
+ late Terminal terminal;
+ late AnalyzeCommand command;
+ late CommandRunner<void> runner;
+ late Directory tempDir;
+ late FakeAndroidBuilder builder;
+
+ setUpAll(() {
+ Cache.disableLocking();
+ });
+
+ setUp(() async {
+ fileSystem = MemoryFileSystem.test();
+ platform = FakePlatform();
+ logger = BufferLogger.test();
+ processManager = FakeProcessManager.empty();
+ terminal = Terminal.test();
+ command = AnalyzeCommand(
+ artifacts: Artifacts.test(),
+ fileSystem: fileSystem,
+ logger: logger,
+ platform: platform,
+ processManager: processManager,
+ terminal: terminal,
+ allProjectValidators: <ProjectValidator>[],
+ suppressAnalytics: true,
+ );
+ runner = createTestCommandRunner(command);
+ tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
+ tempDir.childDirectory('android').createSync();
+
+ // Setup repo roots
+ const String homePath = '/home/user/flutter';
+ Cache.flutterRoot = homePath;
+ for (final String dir in <String>['dev', 'examples', 'packages']) {
+ fileSystem.directory(homePath).childDirectory(dir).createSync(recursive: true);
+ }
+ builder = FakeAndroidBuilder();
+
+ });
+
+ testUsingContext('can list build variants', () async {
+ builder.variants = <String>['debug', 'release'];
+ await runner.run(<String>['analyze', '--android', '--list-build-variants', tempDir.path]);
+ expect(logger.statusText, contains('["debug","release"]'));
+ }, overrides: <Type, Generator>{
+ AndroidBuilder: () => builder,
+ });
+
+ testUsingContext('throw if provide multiple path', () async {
+ final Directory anotherTempDir = fileSystem.systemTempDirectory.createTempSync('another');
+ await expectLater(
+ runner.run(<String>['analyze', '--android', '--list-build-variants', tempDir.path, anotherTempDir.path]),
+ throwsA(
+ isA<Exception>().having(
+ (Exception e) => e.toString(),
+ 'description',
+ contains('The Android analyze can process only one directory path'),
+ ),
+ ),
+ );
+ });
+
+ testUsingContext('can output app link settings', () async {
+ const String buildVariant = 'release';
+ await runner.run(<String>['analyze', '--android', '--output-app-link-settings', '--build-variant=$buildVariant', tempDir.path]);
+ expect(builder.outputVariant, buildVariant);
+ }, overrides: <Type, Generator>{
+ AndroidBuilder: () => builder,
+ });
+
+ testUsingContext('output app link settings throws if no build variant', () async {
+ await expectLater(
+ runner.run(<String>['analyze', '--android', '--output-app-link-settings', tempDir.path]),
+ throwsA(
+ isA<Exception>().having(
+ (Exception e) => e.toString(),
+ 'description',
+ contains('"--build-variant" must be provided'),
+ ),
+ ),
+ );
+ });
+ });
+}
+
+class FakeAndroidBuilder extends Fake implements AndroidBuilder {
+ List<String> variants = const <String>[];
+ String? outputVariant;
+
+ @override
+ Future<List<String>> getBuildVariants({required FlutterProject project}) async {
+ return variants;
+ }
+
+ @override
+ Future<void> outputsAppLinkSettings(String buildVariant, {required FlutterProject project}) async {
+ outputVariant = buildVariant;
+ }
+}