Unverified Commit 61242fa1 authored by chunhtai's avatar chunhtai Committed by GitHub

Updates app link gradle tasks and remove vm services (#131805)

1. Remove vm service registration
2. combine print<variant>ApplicationId and print<variant>AppLinkDomain into one task dump<variant>AppLinkSettings, which dump all the data in a json file

The deeplink validation tool will be a static app in devtool instead of regular app. A Static app doesn't require a running app; therefore, we can't call these API through vmservices. I decided to convert these API into flutter analyzer command, which will be done in a separate PR https://github.com/flutter/flutter/pull/131009.

The reason these print tasks are converted into file dumps is to reduce the amount of data encoding and decoding. Instead of passing data through stdout, the devtool can read the files generated by gradle tasks instead.
parent 6cefc2ea
// 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 'package:meta/meta.dart';
/// A data class for app links related project settings.
///
/// See https://developer.android.com/training/app-links.
@immutable
class AndroidAppLinkSettings {
const AndroidAppLinkSettings({
required this.applicationId,
required this.domains,
});
/// The application id of the android sub-project.
final String applicationId;
/// The associated web domains of the android sub-project.
final List<String> domains;
}
......@@ -43,14 +43,8 @@ abstract class AndroidBuilder {
/// Returns a list of available build variant from the Android project.
Future<List<String>> getBuildVariants({required FlutterProject project});
/// Returns the application id for the given build variant.
Future<String> getApplicationIdForVariant(
String buildVariant, {
required FlutterProject project,
});
/// Returns a list of app link domains for the given build variant.
Future<List<String>> getAppLinkDomainsForVariant(
/// Outputs app link related project settings into a json file.
Future<void> outputsAppLinkSettings(
String buildVariant, {
required FlutterProject project,
});
......
......@@ -52,31 +52,8 @@ final RegExp _kBuildVariantRegex = RegExp('^BuildVariant: (?<$_kBuildVariantRege
const String _kBuildVariantRegexGroupName = 'variant';
const String _kBuildVariantTaskName = 'printBuildVariants';
/// The regex to grab variant names from print${BuildVariant}ApplicationId gradle task
///
/// The task is defined in flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
///
/// The expected output from the task should be similar to:
///
/// ApplicationId: com.example.my_id
final RegExp _kApplicationIdRegex = RegExp('^ApplicationId: (?<$_kApplicationIdRegexGroupName>.*)\$');
const String _kApplicationIdRegexGroupName = 'applicationId';
String _getPrintApplicationIdTaskFor(String buildVariant) {
return _taskForBuildVariant('print', buildVariant, 'ApplicationId');
}
/// The regex to grab app link domains from print${BuildVariant}AppLinkDomains gradle task
///
/// The task is defined in flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
///
/// The expected output from the task should be similar to:
///
/// Domain: domain.com
/// Domain: another-domain.dev
final RegExp _kAppLinkDomainsRegex = RegExp('^Domain: (?<$_kAppLinkDomainsGroupName>.*)\$');
const String _kAppLinkDomainsGroupName = 'domain';
String _getPrintAppLinkDomainsTaskFor(String buildVariant) {
return _taskForBuildVariant('print', buildVariant, 'AppLinkDomains');
String _getOutputAppLinkSettingsTaskFor(String buildVariant) {
return _taskForBuildVariant('output', buildVariant, 'AppLinkSettings');
}
/// The directory where the APK artifact is generated.
......@@ -817,11 +794,11 @@ class AndroidGradleBuilder implements AndroidBuilder {
}
@override
Future<String> getApplicationIdForVariant(
Future<void> outputsAppLinkSettings(
String buildVariant, {
required FlutterProject project,
}) async {
final String taskName = _getPrintApplicationIdTaskFor(buildVariant);
final String taskName = _getOutputAppLinkSettingsTaskFor(buildVariant);
final Stopwatch sw = Stopwatch()
..start();
final RunResult result = await _runGradleTask(
......@@ -829,50 +806,12 @@ class AndroidGradleBuilder implements AndroidBuilder {
options: const <String>['-q'],
project: project,
);
_usage.sendTiming('print', 'application id', sw.elapsed);
_usage.sendTiming('outputs', 'app link settings', sw.elapsed);
if (result.exitCode != 0) {
_logger.printStatus(result.stdout, wrap: false);
_logger.printError(result.stderr, wrap: false);
return '';
}
for (final String line in LineSplitter.split(result.stdout)) {
final RegExpMatch? match = _kApplicationIdRegex.firstMatch(line);
if (match != null) {
return match.namedGroup(_kApplicationIdRegexGroupName)!;
}
}
return '';
}
@override
Future<List<String>> getAppLinkDomainsForVariant(
String buildVariant, {
required FlutterProject project,
}) async {
final String taskName = _getPrintAppLinkDomainsTaskFor(buildVariant);
final Stopwatch sw = Stopwatch()
..start();
final RunResult result = await _runGradleTask(
taskName,
options: const <String>['-q'],
project: project,
);
_usage.sendTiming('print', 'application id', sw.elapsed);
if (result.exitCode != 0) {
_logger.printStatus(result.stdout, wrap: false);
_logger.printError(result.stderr, wrap: false);
return const <String>[];
}
final List<String> domains = <String>[];
for (final String line in LineSplitter.split(result.stdout)) {
final RegExpMatch? match = _kAppLinkDomainsRegex.firstMatch(line);
if (match != null) {
domains.add(match.namedGroup(_kAppLinkDomainsGroupName)!);
}
}
return domains;
}
}
......
......@@ -7,7 +7,6 @@ import 'package:xml/xml.dart';
import 'package:yaml/yaml.dart';
import '../src/convert.dart';
import 'android/android_app_link_settings.dart';
import 'android/android_builder.dart';
import 'android/gradle_utils.dart' as gradle;
import 'base/common.dart';
......@@ -486,20 +485,15 @@ class AndroidProject extends FlutterProjectPlatform {
return androidBuilder!.getBuildVariants(project: parent);
}
/// Returns app link related project settings for a given build variant.
/// Outputs app link related settings into a json file.
///
/// Use [getBuildVariants] to get all of the available build variants.
Future<AndroidAppLinkSettings> getAppLinksSettings({required String variant}) async {
/// The file is stored in
/// `<project>/build/app/app-link-settings-<variant>.json`.
Future<void> outputsAppLinkSettings({required String variant}) async {
if (!existsSync() || androidBuilder == null) {
return const AndroidAppLinkSettings(
applicationId: '',
domains: <String>[],
);
return;
}
return AndroidAppLinkSettings(
applicationId: await androidBuilder!.getApplicationIdForVariant(variant, project: parent),
domains: await androidBuilder!.getAppLinkDomainsForVariant(variant, project: parent),
);
await androidBuilder!.outputsAppLinkSettings(variant, project: parent);
}
bool _computeSupportedVersion() {
......
......@@ -7,7 +7,6 @@ import 'dart:async';
import 'package:meta/meta.dart' show visibleForTesting;
import 'package:vm_service/vm_service.dart' as vm_service;
import 'android/android_app_link_settings.dart';
import 'base/common.dart';
import 'base/context.dart';
import 'base/io.dart' as io;
......@@ -43,9 +42,7 @@ const String kCompileExpressionServiceName = 'compileExpression';
const String kFlutterMemoryInfoServiceName = 'flutterMemoryInfo';
const String kFlutterGetSkSLServiceName = 'flutterGetSkSL';
const String kFlutterGetIOSBuildOptionsServiceName = 'flutterGetIOSBuildOptions';
const String kFlutterGetAndroidBuildVariantsServiceName = 'flutterGetAndroidBuildVariants';
const String kFlutterGetIOSUniversalLinkSettingsServiceName = 'flutterGetIOSUniversalLinkSettings';
const String kFlutterGetAndroidAppLinkSettingsName = 'flutterGetAndroidAppLinkSettings';
/// The error response code from an unrecoverable compilation failure.
const int kIsolateReloadBarred = 1005;
......@@ -341,19 +338,6 @@ Future<vm_service.VmService> setUpVmService({
vmService.registerService(kFlutterGetIOSBuildOptionsServiceName, kFlutterToolAlias),
);
vmService.registerServiceCallback(kFlutterGetAndroidBuildVariantsServiceName, (Map<String, Object?> params) async {
final List<String> options = await flutterProject.android.getBuildVariants();
return <String, Object>{
'result': <String, Object>{
kResultType: kResultTypeSuccess,
'variants': options,
},
};
});
registrationRequests.add(
vmService.registerService(kFlutterGetAndroidBuildVariantsServiceName, kFlutterToolAlias),
);
vmService.registerServiceCallback(kFlutterGetIOSUniversalLinkSettingsServiceName, (Map<String, Object?> params) async {
final XcodeUniversalLinkSettings settings = await flutterProject.ios.universalLinkSettings(
configuration: params['configuration']! as String,
......@@ -372,21 +356,6 @@ Future<vm_service.VmService> setUpVmService({
registrationRequests.add(
vmService.registerService(kFlutterGetIOSUniversalLinkSettingsServiceName, kFlutterToolAlias),
);
vmService.registerServiceCallback(kFlutterGetAndroidAppLinkSettingsName, (Map<String, Object?> params) async {
final String variant = params['variant']! as String;
final AndroidAppLinkSettings settings = await flutterProject.android.getAppLinksSettings(variant: variant);
return <String, Object>{
'result': <String, Object>{
kResultType: kResultTypeSuccess,
'applicationId': settings.applicationId,
'domains': settings.domains,
},
};
});
registrationRequests.add(
vmService.registerService(kFlutterGetAndroidAppLinkSettingsName, kFlutterToolAlias),
);
}
if (printStructuredErrorLogMethod != null) {
......
......@@ -885,113 +885,13 @@ Gradle Crashed
command: <String>[
'gradlew',
'-q',
'printFreeDebugApplicationId',
'outputFreeDebugAppLinkSettings',
],
stdout: '''
ApplicationId: com.example.id
''',
));
final String actual = await builder.getApplicationIdForVariant(
'freeDebug',
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
);
expect(actual, 'com.example.id');
}, overrides: <Type, Generator>{
AndroidStudio: () => FakeAndroidStudio(),
});
testUsingContext('can call custom gradle task getApplicationIdForVariant with unknown crash', () async {
final AndroidGradleBuilder builder = AndroidGradleBuilder(
java: FakeJava(),
logger: logger,
processManager: processManager,
fileSystem: fileSystem,
artifacts: Artifacts.test(),
usage: testUsage,
gradleUtils: FakeGradleUtils(),
platform: FakePlatform(),
androidStudio: FakeAndroidStudio(),
);
processManager.addCommand(const FakeCommand(
command: <String>[
'gradlew',
'-q',
'printFreeDebugApplicationId',
],
stdout: '''
unknown crash
''',
));
final String actual = await builder.getApplicationIdForVariant(
'freeDebug',
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
);
expect(actual, '');
}, overrides: <Type, Generator>{
AndroidStudio: () => FakeAndroidStudio(),
});
testUsingContext('can call custom gradle task getAppLinkDomainsForVariant and parse the result', () async {
final AndroidGradleBuilder builder = AndroidGradleBuilder(
java: FakeJava(),
logger: logger,
processManager: processManager,
fileSystem: fileSystem,
artifacts: Artifacts.test(),
usage: testUsage,
gradleUtils: FakeGradleUtils(),
platform: FakePlatform(),
androidStudio: FakeAndroidStudio(),
);
processManager.addCommand(const FakeCommand(
command: <String>[
'gradlew',
'-q',
'printFreeDebugAppLinkDomains',
],
stdout: '''
Domain: example.com
Domain: example2.com
''',
));
final List<String> actual = await builder.getAppLinkDomainsForVariant(
'freeDebug',
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
);
expect(actual, <String>['example.com', 'example2.com']);
}, overrides: <Type, Generator>{
AndroidStudio: () => FakeAndroidStudio(),
});
testUsingContext('can call custom gradle task getAppLinkDomainsForVariant with unknown crash', () async {
final AndroidGradleBuilder builder = AndroidGradleBuilder(
java: FakeJava(),
logger: logger,
processManager: processManager,
fileSystem: fileSystem,
artifacts: Artifacts.test(),
usage: testUsage,
gradleUtils: FakeGradleUtils(),
platform: FakePlatform(),
androidStudio: FakeAndroidStudio(),
);
processManager.addCommand(const FakeCommand(
command: <String>[
'gradlew',
'-q',
'printFreeDebugAppLinkDomains',
],
stdout: '''
unknown crash
''',
));
final List<String> actual = await builder.getAppLinkDomainsForVariant(
await builder.outputsAppLinkSettings(
'freeDebug',
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
);
expect(actual.isEmpty, isTrue);
}, overrides: <Type, Generator>{
AndroidStudio: () => FakeAndroidStudio(),
});
......
......@@ -78,18 +78,10 @@ const List<VmServiceExpectation> kAttachIsolateExpectations =
'service': kFlutterGetIOSBuildOptionsServiceName,
'alias': kFlutterToolAlias,
}),
FakeVmServiceRequest(method: 'registerService', args: <String, Object>{
'service': kFlutterGetAndroidBuildVariantsServiceName,
'alias': kFlutterToolAlias,
}),
FakeVmServiceRequest(method: 'registerService', args: <String, Object>{
'service': kFlutterGetIOSUniversalLinkSettingsServiceName,
'alias': kFlutterToolAlias,
}),
FakeVmServiceRequest(method: 'registerService', args: <String, Object>{
'service': kFlutterGetAndroidAppLinkSettingsName,
'alias': kFlutterToolAlias,
}),
FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
......
......@@ -96,17 +96,6 @@ void main() {
expect(mockVMService.services, containsPair(kFlutterGetIOSBuildOptionsServiceName, kFlutterToolAlias));
});
testWithoutContext('VmService registers flutterGetAndroidBuildVariants service', () async {
final MockVMService mockVMService = MockVMService();
final FlutterProject mockedFlutterProject = MockFlutterProject();
await setUpVmService(
flutterProject: mockedFlutterProject,
vmService: mockVMService,
);
expect(mockVMService.services, containsPair(kFlutterGetAndroidBuildVariantsServiceName, kFlutterToolAlias));
});
testWithoutContext('VM Service registers flutterGetSkSL service', () async {
final MockVMService mockVMService = MockVMService();
await setUpVmService(
......@@ -313,24 +302,6 @@ void main() {
expect(result['schemes'], expectedProjectInfo.schemes);
});
testWithoutContext('VmService forward flutterGetAndroidBuildVariants request and response correctly', () async {
final MockVMService vmService = MockVMService();
final List<String> expectedOptions = <String>['debug', 'release', 'profile'];
final FlutterProject mockedFlutterProject = MockFlutterProject(
mockedAndroid: MockAndroidProject(mockedOptions: expectedOptions),
);
await setUpVmService(
flutterProject: mockedFlutterProject,
vmService: vmService
);
final vm_service.ServiceCallback cb = vmService.serviceCallBacks[kFlutterGetAndroidBuildVariantsServiceName]!;
final Map<String, dynamic> response = await cb(<String, dynamic>{});
final Map<String, dynamic> result = response['result']! as Map<String, dynamic>;
expect(result[kResultType], kResultTypeSuccess);
expect(result['variants'], expectedOptions);
});
testWithoutContext('VmService forward flutterGetIOSBuildOptions request and response correctly when no iOS project', () async {
final MockVMService vmService = MockVMService();
final FlutterProject mockedFlutterProject = MockFlutterProject(
......@@ -940,15 +911,10 @@ void main() {
class MockFlutterProject extends Fake implements FlutterProject {
MockFlutterProject({
IosProject? mockedIos,
AndroidProject? mockedAndroid,
}) : ios = mockedIos ?? MockIosProject(),
android = mockedAndroid ?? MockAndroidProject();
}) : ios = mockedIos ?? MockIosProject();
@override
final IosProject ios;
@override
final AndroidProject android;
}
class MockIosProject extends Fake implements IosProject {
......@@ -960,15 +926,6 @@ class MockIosProject extends Fake implements IosProject {
Future<XcodeProjectInfo?> projectInfo() async => mockedInfo;
}
class MockAndroidProject extends Fake implements AndroidProject {
MockAndroidProject({this.mockedOptions = const <String>[]});
final List<String> mockedOptions;
@override
Future<List<String>> getBuildVariants() async => mockedOptions;
}
class MockLogger extends Fake implements Logger { }
class MockVMService extends Fake implements vm_service.VmService {
......
......@@ -5,7 +5,6 @@
import 'dart:convert';
import 'dart:io' as io;
import 'package:collection/collection.dart';
import 'package:file/file.dart';
import 'package:flutter_tools/src/android/gradle_utils.dart'
show getGradlewFileName;
......@@ -136,8 +135,15 @@ void main() {
tryToDelete(tempDir);
});
void testDeeplink(dynamic deeplink, String scheme, String host, String path) {
deeplink as Map<String, dynamic>;
expect(deeplink['scheme'], scheme);
expect(deeplink['host'], host);
expect(deeplink['path'], path);
}
testWithoutContext(
'gradle task exists named print<mode>AppLinkDomains that prints app link domains', () async {
'gradle task outputs<mode>AppLinkSettings works when a project has app links', () async {
// Create a new flutter project.
final String flutterBin =
fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
......@@ -173,17 +179,61 @@ void main() {
'.${platform.pathSeparator}${getGradlewFileName(platform)}',
...getLocalEngineArguments(),
'-q', // quiet output.
'printDebugAppLinkDomains',
'outputDebugAppLinkSettings',
], workingDirectory: androidApp.path);
expect(result, const ProcessResultMatcher());
final io.File fileDump = tempDir.childDirectory('build').childDirectory('app').childFile('app-link-settings-debug.json');
expect(fileDump.existsSync(), true);
final Map<String, dynamic> json = jsonDecode(fileDump.readAsStringSync()) as Map<String, dynamic>;
expect(json['applicationId'], 'com.example.testapp');
final List<dynamic> deeplinks = json['deeplinks']! as List<dynamic>;
expect(deeplinks.length, 5);
testDeeplink(deeplinks[0], 'http', 'pure-http.com', '.*');
testDeeplink(deeplinks[1], 'custom', 'custom.com', '.*');
testDeeplink(deeplinks[2], 'custom', 'hybrid.com', '.*');
testDeeplink(deeplinks[3], 'http', 'hybrid.com', '.*');
testDeeplink(deeplinks[4], 'http', 'non-auto-verify.com', '.*');
});
testWithoutContext(
'gradle task outputs<mode>AppLinkSettings works when a project does not have app link', () async {
// Create a new flutter project.
final String flutterBin =
fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
ProcessResult result = await processManager.run(<String>[
flutterBin,
'create',
tempDir.path,
'--project-name=testapp',
], workingDirectory: tempDir.path);
expect(result, const ProcessResultMatcher());
// Ensure that gradle files exists from templates.
result = await processManager.run(<String>[
flutterBin,
'build',
'apk',
'--config-only',
], workingDirectory: tempDir.path);
expect(result, const ProcessResultMatcher());
final Directory androidApp = tempDir.childDirectory('android');
result = await processManager.run(<String>[
'.${platform.pathSeparator}${getGradlewFileName(platform)}',
...getLocalEngineArguments(),
'-q', // quiet output.
'outputDebugAppLinkSettings',
], workingDirectory: androidApp.path);
expect(result, const ProcessResultMatcher());
const List<String> expectedLines = <String>[
// Should only pick up the pure and hybrid intent filters
'Domain: pure-http.com',
'Domain: hybrid.com',
];
final List<String> actualLines = LineSplitter.split(result.stdout.toString()).toList();
expect(const ListEquality<String>().equals(actualLines, expectedLines), isTrue);
final io.File fileDump = tempDir.childDirectory('build').childDirectory('app').childFile('app-link-settings-debug.json');
expect(fileDump.existsSync(), true);
final Map<String, dynamic> json = jsonDecode(fileDump.readAsStringSync()) as Map<String, dynamic>;
expect(json['applicationId'], 'com.example.testapp');
final List<dynamic> deeplinks = json['deeplinks']! as List<dynamic>;
expect(deeplinks.length, 0);
});
}
// 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:convert';
import 'package:collection/collection.dart';
import 'package:file/file.dart';
import 'package:flutter_tools/src/android/gradle_utils.dart'
show getGradlewFileName;
import 'package:flutter_tools/src/base/io.dart';
import '../src/common.dart';
import 'test_utils.dart';
void main() {
late Directory tempDir;
setUp(() async {
tempDir = createResolvedTempDirectorySync('run_test.');
});
tearDown(() async {
tryToDelete(tempDir);
});
testWithoutContext(
'gradle task exists named print<mode>ApplicationId that prints application id', () async {
// Create a new flutter project.
final String flutterBin =
fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
ProcessResult result = await processManager.run(<String>[
flutterBin,
'create',
tempDir.path,
'--project-name=testapp',
], workingDirectory: tempDir.path);
expect(result, const ProcessResultMatcher());
// Ensure that gradle files exists from templates.
result = await processManager.run(<String>[
flutterBin,
'build',
'apk',
'--config-only',
], workingDirectory: tempDir.path);
expect(result, const ProcessResultMatcher());
final Directory androidApp = tempDir.childDirectory('android');
result = await processManager.run(<String>[
'.${platform.pathSeparator}${getGradlewFileName(platform)}',
...getLocalEngineArguments(),
'-q', // quiet output.
'printDebugApplicationId',
], workingDirectory: androidApp.path);
// Verify that gradlew has a javaVersion task.
expect(result, const ProcessResultMatcher());
// Verify the format is a number on its own line.
const List<String> expectedLines = <String>[
'ApplicationId: com.example.testapp',
];
final List<String> actualLines = LineSplitter.split(result.stdout.toString()).toList();
expect(const ListEquality<String>().equals(actualLines, expectedLines), isTrue);
});
}
......@@ -41,10 +41,11 @@ class FakeAndroidBuilder implements AndroidBuilder {
Future<List<String>> getBuildVariants({required FlutterProject project}) async => const <String>[];
@override
Future<List<String>> getAppLinkDomainsForVariant(String buildVariant, {required FlutterProject project}) async => const <String>[];
Future<void> outputsAppLinkSettings(
String buildVariant, {
required FlutterProject project,
}) async {}
@override
Future<String> getApplicationIdForVariant(String buildVariant, {required FlutterProject project}) async => '';
}
/// Creates a [FlutterProject] in a directory named [flutter_project]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment