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
......@@ -2,14 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import static groovy.io.FileType.FILES
import com.android.build.OutputFile
import groovy.json.JsonSlurper
import java.nio.file.Path
import groovy.json.JsonGenerator
import groovy.xml.QName
import java.nio.file.Paths
import java.util.regex.Matcher
import java.util.regex.Pattern
import java.util.Set
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
......@@ -30,7 +28,6 @@ import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.bundling.Jar
import org.gradle.internal.os.OperatingSystem
import org.gradle.util.VersionNumber
/**
* For apps only. Provides the flutter extension used in app/build.gradle.
......@@ -750,80 +747,90 @@ class FlutterPlugin implements Plugin<Project> {
}
}
// Add a task that can be called on Flutter projects that prints application id of a build
// variant.
//
// This task prints the application id in this format:
//
// ApplicationId: com.example.my_id
//
// Format of the output of this task is used by `AndroidProject.getApplicationIdForVariant`.
private static void addTasksForPrintApplicationId(Project project) {
project.android.applicationVariants.all { variant ->
// Warning: The name of this task is used by `AndroidProject.getApplicationIdForVariant`.
project.tasks.register("print${variant.name.capitalize()}ApplicationId") {
description "Prints out application id for the given build variant of this Android project"
doLast {
println "ApplicationId: ${variant.applicationId}";
}
}
}
}
// Add a task that can be called on Flutter projects that prints app link domains of a build
// variant.
// Add a task that can be called on Flutter projects that outputs app link related project
// settings into a json file.
//
// The app link domains refer to the host attributes of data tags in the apps' intent filters
// that support http/https schemes. See
// https://developer.android.com/guide/topics/manifest/intent-filter-element.
// See https://developer.android.com/training/app-links/ for more information about app link.
//
// This task prints app link domains in this format:
// The json will be stored in <project>/build/app/app-link-settings-<variant>.json
//
// Domain: domain.com
// Domain: another-domain.dev
// An example json:
// {
// applicationId: "com.example.app",
// deeplinks: [
// {"scheme":"http", "host":"example.com", "path":".*"},
// {"scheme":"https","host":"example.com","path":".*"}
// ]
// }
//
// Format of the output of this task is used by `AndroidProject.getAppLinkDomainsForVariant`.
private static void addTasksForPrintAppLinkDomains(Project project) {
// The output file is parsed and used by devtool.
private static void addTasksForOutputsAppLinkSettings(Project project) {
project.android.applicationVariants.all { variant ->
// Warning: The name of this task is used by `AndroidProject.getAppLinkDomainsForVariant`.
project.tasks.register("print${variant.name.capitalize()}AppLinkDomains") {
description "Prints out app links domain for the given build variant of this Android project"
// Warning: The name of this task is used by AndroidBuilder.outputsAppLinkSettings
project.tasks.register("output${variant.name.capitalize()}AppLinkSettings") {
description "stores app links settings for the given build variant of this Android project into a json file."
variant.outputs.all { output ->
// Deeplinks are defined in AndroidManifest.xml and is only available after
// `processResourcesProvider`.
def processResources = output.hasProperty("processResourcesProvider") ?
output.processResourcesProvider.get() : output.processResources
dependsOn processResources.name
}
doLast {
def appLinkSettings = new AppLinkSettings()
appLinkSettings.applicationId = variant.applicationId
appLinkSettings.deeplinks = [] as Set<Deeplink>
variant.outputs.all { output ->
def processResources = output.hasProperty("processResourcesProvider") ?
output.processResourcesProvider.get() : output.processResources
def manifest = new XmlParser().parse(processResources.manifestFile)
manifest.application.activity.each { activity ->
// Find intent filters that have autoVerify = true and support http/https
// scheme.
activity.'intent-filter'.findAll { filter ->
def hasAutoVerify = filter.attributes().any { entry ->
return entry.key.getLocalPart() == "autoVerify" && entry.value
}
def hasHttpOrHttps = filter.data.any { data ->
data.attributes().any { entry ->
return entry.key.getLocalPart() == "scheme" &&
(entry.value == "http" || entry.value == "https")
}
}
return hasAutoVerify && hasHttpOrHttps
}.each { appLinkIntent ->
activity.'intent-filter'.each { appLinkIntent ->
// Print out the host attributes in data tags.
def schemes = [] as Set<String>
def hosts = [] as Set<String>
def paths = [] as Set<String>
appLinkIntent.data.each { data ->
data.attributes().each { entry ->
if (entry.key.getLocalPart() == "host") {
println "Domain: ${entry.value}"
if (entry.key instanceof QName) {
switch (entry.key.getLocalPart()) {
case "scheme":
schemes.add(entry.value)
break
case "host":
hosts.add(entry.value)
break
case "pathAdvancedPattern":
case "pathPattern":
case "path":
paths.add(entry.value)
break
case "pathPrefix":
paths.add("${entry.value}.*")
break
case "pathSuffix":
paths.add(".*${entry.value}")
break
}
}
}
}
schemes.each {scheme ->
hosts.each { host ->
if (!paths) {
appLinkSettings.deeplinks.add(new Deeplink(scheme: scheme, host: host, path: ".*"))
} else {
paths.each { path ->
appLinkSettings.deeplinks.add(new Deeplink(scheme: scheme, host: host, path: path))
}
}
}
}
}
}
}
def generator = new JsonGenerator.Options().build()
new File(project.buildDir, "app-link-settings-${variant.name}.json").write(generator.toJson(appLinkSettings))
}
}
}
......@@ -1004,8 +1011,7 @@ class FlutterPlugin implements Plugin<Project> {
addTaskForJavaVersion(project)
if(isFlutterAppProject()) {
addTaskForPrintBuildVariants(project)
addTasksForPrintApplicationId(project)
addTasksForPrintAppLinkDomains(project)
addTasksForOutputsAppLinkSettings(project)
}
def targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant ->
......@@ -1234,6 +1240,24 @@ class FlutterPlugin implements Plugin<Project> {
}
}
class AppLinkSettings {
String applicationId
Set<Deeplink> deeplinks
}
class Deeplink {
String scheme, host, path
boolean equals(o) {
if (o == null)
throw new NullPointerException()
if (o.getClass() != getClass())
return false
return scheme == o.scheme &&
host == o.host &&
path == o.path
}
}
abstract class BaseFlutterTask extends DefaultTask {
@Internal
File flutterRoot
......
// 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