Unverified Commit 5328bd9a authored by chunhtai's avatar chunhtai Committed by GitHub

Adds vmservices to retrieve android applink settings (#125998)

fixes https://github.com/flutter/flutter/issues/120408

Added two gradle tasks, one for grabing the application id, one for grabbing app link domains.

Added a new vmservices to call these two gradle tasks and return the result.

The expected work flow is that the devtool will first call a vmservices to grab all avaliable build variants. It will then choose one of the build variant and call this new services to get application id and app link domains.
parent 2754514c
......@@ -731,6 +731,85 @@ 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.
//
// 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.
//
// This task prints app link domains in this format:
//
// Domain: domain.com
// Domain: another-domain.dev
//
// Format of the output of this task is used by `AndroidProject.getAppLinkDomainsForVariant`.
private static void addTasksForPrintAppLinkDomains(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"
variant.outputs.all { output ->
def processResources = output.hasProperty("processResourcesProvider") ?
output.processResourcesProvider.get() : output.processResources
dependsOn processResources.name
}
doLast {
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 ->
// Print out the host attributes in data tags.
appLinkIntent.data.each { data ->
data.attributes().each { entry ->
if (entry.key.getLocalPart() == "host") {
println "Domain: ${entry.value}"
}
}
}
}
}
}
}
}
}
}
/**
* Returns a Flutter build mode suitable for the specified Android buildType.
*
......@@ -904,7 +983,11 @@ class FlutterPlugin implements Plugin<Project> {
validateDeferredComponentsValue = project.property('validate-deferred-components').toBoolean()
}
addTaskForJavaVersion(project)
if(isFlutterAppProject()) {
addTaskForPrintBuildVariants(project)
addTasksForPrintApplicationId(project)
addTasksForPrintAppLinkDomains(project)
}
def targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
......
// 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;
}
......@@ -42,4 +42,16 @@ 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(
String buildVariant, {
required FlutterProject project,
});
}
......@@ -38,9 +38,9 @@ import 'migrations/android_studio_java_gradle_conflict_migration.dart';
import 'migrations/top_level_gradle_build_file_migration.dart';
import 'multidex.dart';
/// The regex to grab variant names from printVariants gradle task
/// The regex to grab variant names from printBuildVariants gradle task
///
/// The task is defined in flutter/packages/flutter_tools/gradle/flutter.gradle.
/// 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:
///
......@@ -49,6 +49,34 @@ import 'multidex.dart';
/// BuildVariant: profile
final RegExp _kBuildVariantRegex = RegExp('^BuildVariant: (?<$_kBuildVariantRegexGroupName>.*)\$');
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');
}
/// The directory where the APK artifact is generated.
Directory getApkDirectory(FlutterProject project) {
......@@ -89,9 +117,14 @@ Directory getRepoDirectory(Directory buildDirectory) {
String _taskFor(String prefix, BuildInfo buildInfo) {
final String buildType = camelCase(buildInfo.modeName);
final String productFlavor = buildInfo.flavor ?? '';
return '$prefix${sentenceCase(productFlavor)}${sentenceCase(buildType)}';
return _taskForBuildVariant(prefix, '$productFlavor${sentenceCase(buildType)}');
}
String _taskForBuildVariant(String prefix, String buildVariant, [String suffix = '']) {
return '$prefix${sentenceCase(buildVariant)}$suffix';
}
/// Returns the task to build an APK.
@visibleForTesting
String getAssembleTaskFor(BuildInfo buildInfo) {
......@@ -240,6 +273,34 @@ class AndroidGradleBuilder implements AndroidBuilder {
);
}
Future<RunResult> _runGradleTask(
String taskName, {
List<String> options = const <String>[],
required FlutterProject project
}) async {
final Status status = _logger.startProgress(
"Running Gradle task '$taskName'...",
);
final List<String> command = <String>[
_gradleUtils.getExecutable(project),
...options, // suppresses gradle output.
taskName,
];
RunResult result;
try {
result = await _processUtils.run(
command,
workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: _java?.environment,
);
} finally {
status.stop();
}
return result;
}
/// Builds an app.
///
/// * [project] is typically [FlutterProject.current()].
......@@ -727,28 +788,14 @@ class AndroidGradleBuilder implements AndroidBuilder {
@override
Future<List<String>> getBuildVariants({required FlutterProject project}) async {
final Status status = _logger.startProgress(
"Running Gradle task 'printBuildVariants'...",
);
final List<String> command = <String>[
_gradleUtils.getExecutable(project),
'-q', // suppresses gradle output.
'printBuildVariants',
];
final Stopwatch sw = Stopwatch()
..start();
RunResult result;
try {
result = await _processUtils.run(
command,
workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: _java?.environment,
final RunResult result = await _runGradleTask(
_kBuildVariantTaskName,
options: const <String>['-q'],
project: project,
);
} finally {
status.stop();
}
_usage.sendTiming('print', 'android build variants', sw.elapsed);
if (result.exitCode != 0) {
......@@ -765,6 +812,65 @@ class AndroidGradleBuilder implements AndroidBuilder {
}
return options;
}
@override
Future<String> getApplicationIdForVariant(
String buildVariant, {
required FlutterProject project,
}) async {
final String taskName = _getPrintApplicationIdTaskFor(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 '';
}
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;
}
}
/// Prints how to consume the AAR from a host app.
......
......@@ -7,6 +7,7 @@ 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';
......@@ -477,6 +478,7 @@ class AndroidProject extends FlutterProjectPlatform {
/// Returns true if the current version of the Gradle plugin is supported.
late final bool isSupportedVersion = _computeSupportedVersion();
/// Gets all build variants of this project.
Future<List<String>> getBuildVariants() async {
if (!existsSync() || androidBuilder == null) {
return const <String>[];
......@@ -484,6 +486,22 @@ class AndroidProject extends FlutterProjectPlatform {
return androidBuilder!.getBuildVariants(project: parent);
}
/// Returns app link related project settings for a given build variant.
///
/// Use [getBuildVariants] to get all of the available build variants.
Future<AndroidAppLinkSettings> getAppLinksSettings({required String variant}) async {
if (!existsSync() || androidBuilder == null) {
return const AndroidAppLinkSettings(
applicationId: '',
domains: <String>[],
);
}
return AndroidAppLinkSettings(
applicationId: await androidBuilder!.getApplicationIdForVariant(variant, project: parent),
domains: await androidBuilder!.getAppLinkDomainsForVariant(variant, project: parent),
);
}
bool _computeSupportedVersion() {
final FileSystem fileSystem = hostAppGradleRoot.fileSystem;
final File plugin = hostAppGradleRoot.childFile(
......
......@@ -7,6 +7,7 @@ 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;
......@@ -42,6 +43,7 @@ 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;
......@@ -365,6 +367,21 @@ 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) {
......
......@@ -869,6 +869,133 @@ Gradle Crashed
AndroidStudio: () => FakeAndroidStudio(),
});
testUsingContext('can call custom gradle task getApplicationIdForVariant 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',
'printFreeDebugApplicationId',
],
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(
'freeDebug',
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
);
expect(actual.isEmpty, isTrue);
}, overrides: <Type, Generator>{
AndroidStudio: () => FakeAndroidStudio(),
});
testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAar is false", () async {
final AndroidGradleBuilder builder = AndroidGradleBuilder(
java: FakeJava(),
......
......@@ -86,6 +86,10 @@ const List<VmServiceExpectation> kAttachIsolateExpectations =
'service': kFlutterGetIOSUniversalLinkSettingsServiceName,
'alias': kFlutterToolAlias,
}),
FakeVmServiceRequest(method: 'registerService', args: <String, Object>{
'service': kFlutterGetAndroidAppLinkSettingsName,
'alias': kFlutterToolAlias,
}),
FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
......
// 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 '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;
import 'package:flutter_tools/src/base/io.dart';
import 'package:xml/xml.dart';
import '../src/common.dart';
import 'test_utils.dart';
final XmlElement pureHttpIntentFilter = XmlElement(
XmlName('intent-filter'),
<XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
<XmlElement>[
XmlElement(
XmlName('action'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW')],
),
XmlElement(
XmlName('category'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT')],
),
XmlElement(
XmlName('category'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE')],
),
XmlElement(
XmlName('data'),
<XmlAttribute>[
XmlAttribute(XmlName('scheme', 'android'), 'http'),
XmlAttribute(XmlName('host', 'android'), 'pure-http.com'),
],
),
],
);
final XmlElement nonHttpIntentFilter = XmlElement(
XmlName('intent-filter'),
<XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
<XmlElement>[
XmlElement(
XmlName('action'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW')],
),
XmlElement(
XmlName('category'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT')],
),
XmlElement(
XmlName('category'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE')],
),
XmlElement(
XmlName('data'),
<XmlAttribute>[
XmlAttribute(XmlName('scheme', 'android'), 'custom'),
XmlAttribute(XmlName('host', 'android'), 'custom.com'),
],
),
],
);
final XmlElement hybridIntentFilter = XmlElement(
XmlName('intent-filter'),
<XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')],
<XmlElement>[
XmlElement(
XmlName('action'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW')],
),
XmlElement(
XmlName('category'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT')],
),
XmlElement(
XmlName('category'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE')],
),
XmlElement(
XmlName('data'),
<XmlAttribute>[
XmlAttribute(XmlName('scheme', 'android'), 'custom'),
XmlAttribute(XmlName('host', 'android'), 'hybrid.com'),
],
),
XmlElement(
XmlName('data'),
<XmlAttribute>[
XmlAttribute(XmlName('scheme', 'android'), 'http'),
],
),
],
);
final XmlElement nonAutoVerifyIntentFilter = XmlElement(
XmlName('intent-filter'),
<XmlAttribute>[],
<XmlElement>[
XmlElement(
XmlName('action'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW')],
),
XmlElement(
XmlName('category'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT')],
),
XmlElement(
XmlName('category'),
<XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE')],
),
XmlElement(
XmlName('data'),
<XmlAttribute>[
XmlAttribute(XmlName('scheme', 'android'), 'http'),
XmlAttribute(XmlName('host', 'android'), 'non-auto-verify.com'),
],
),
],
);
void main() {
late Directory tempDir;
setUp(() async {
tempDir = createResolvedTempDirectorySync('run_test.');
});
tearDown(() async {
tryToDelete(tempDir);
});
testWithoutContext(
'gradle task exists named print<mode>AppLinkDomains that prints app link domains', () 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.exitCode, 0);
// Adds intent filters for app links
final String androidManifestPath = fileSystem.path.join(tempDir.path, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
final io.File androidManifestFile = io.File(androidManifestPath);
final XmlDocument androidManifest = XmlDocument.parse(androidManifestFile.readAsStringSync());
final XmlElement activity = androidManifest.findAllElements('activity').first;
activity.children.add(pureHttpIntentFilter);
activity.children.add(nonHttpIntentFilter);
activity.children.add(hybridIntentFilter);
activity.children.add(nonAutoVerifyIntentFilter);
androidManifestFile.writeAsStringSync(androidManifest.toString(), flush: true);
// Ensure that gradle files exists from templates.
result = await processManager.run(<String>[
flutterBin,
'build',
'apk',
'--config-only',
], workingDirectory: tempDir.path);
expect(result.exitCode, 0);
final Directory androidApp = tempDir.childDirectory('android');
result = await processManager.run(<String>[
'.${platform.pathSeparator}${getGradlewFileName(platform)}',
...getLocalEngineArguments(),
'-q', // quiet output.
'printDebugAppLinkDomains',
], workingDirectory: androidApp.path);
expect(result.exitCode, 0);
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);
});
}
// 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.exitCode, 0);
// Ensure that gradle files exists from templates.
result = await processManager.run(<String>[
flutterBin,
'build',
'apk',
'--config-only',
], workingDirectory: tempDir.path);
expect(result.exitCode, 0);
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.exitCode, 0);
// 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);
});
}
......@@ -39,6 +39,12 @@ class FakeAndroidBuilder implements AndroidBuilder {
@override
Future<List<String>> getBuildVariants({required FlutterProject project}) async => const <String>[];
@override
Future<List<String>> getAppLinkDomainsForVariant(String buildVariant, {required FlutterProject project}) async => const <String>[];
@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