Skip to content

Commit

Permalink
Allow plugins to use compileSdkPreview (flutter#131901)
Browse files Browse the repository at this point in the history
Fixes flutter#124748

Based (heavily) off flutter#104662
  • Loading branch information
gmackall committed Dec 12, 2023
1 parent eb0890c commit 9a72e1c
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 99 deletions.
30 changes: 30 additions & 0 deletions .ci.yaml
Expand Up @@ -1076,6 +1076,36 @@ targets:
- bin/**
- .ci.yaml

- name: Linux android_preview_tool_integration_tests
bringup: true
recipe: flutter/flutter_drone
timeout: 60
properties:
add_recipes_cq: "true"
# This makes use of UpsideDownCake, a preview version of android. Preview versions eventually
# get removed from the sdk manager, so it is hosted on CIPD to ensure integration testing
# doesn't flake when that happens.
# https://chrome-infra-packages.appspot.com/p/flutter/android/sdk/all/linux-amd64/+/version:udcv1
dependencies: >-
[
{"dependency": "android_sdk", "version": "version:udcv1"},
{"dependency": "chrome_and_driver", "version": "version:119.0.6045.9"},
{"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"},
{"dependency": "cmake", "version": "build_id:8787856497187628321"},
{"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"},
{"dependency": "ninja", "version": "version:1.9.0"},
{"dependency": "open_jdk", "version": "version:11"}
]
shard: android_preview_tool_integration_tests
tags: >
["framework", "hostonly", "shard", "linux"]
test_timeout_secs: "2700"
runIf:
- dev/**
- packages/flutter_tools/**
- bin/**
- .ci.yaml

- name: Linux tool_tests_commands
recipe: flutter/flutter_drone
timeout: 60
Expand Down
15 changes: 15 additions & 0 deletions dev/bots/test.dart
Expand Up @@ -253,6 +253,7 @@ Future<void> main(List<String> args) async {
// web_tool_tests is also used by HHH: https://dart.googlesource.com/recipes/+/refs/heads/master/recipes/dart/flutter_engine.py
'web_tool_tests': _runWebToolTests,
'tool_integration_tests': _runIntegrationToolTests,
'android_preview_tool_integration_tests': _runAndroidPreviewIntegrationToolTests,
'tool_host_cross_arch_tests': _runToolHostCrossArchTests,
// All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=html`
'web_tests': _runWebHtmlUnitTests,
Expand Down Expand Up @@ -477,6 +478,20 @@ Future<void> _runIntegrationToolTests() async {
);
}

Future<void> _runAndroidPreviewIntegrationToolTests() async {
final List<String> allTests = Directory(path.join(_toolsPath, 'test', 'android_preview_integration.shard'))
.listSync(recursive: true).whereType<File>()
.map<String>((FileSystemEntity entry) => path.relative(entry.path, from: _toolsPath))
.toList();

await _runDartTest(
_toolsPath,
forceSingleCore: true,
testPaths: _selectIndexOfTotalSubshard<String>(allTests),
collectMetrics: true,
);
}

Future<void> _runToolTests() async {
await selectSubshard(<String, ShardRunner>{
'general': _runGeneralToolTests,
Expand Down
25 changes: 20 additions & 5 deletions packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
Expand Up @@ -441,7 +441,7 @@ class FlutterPlugin implements Plugin<Project> {
pluginProject.afterEvaluate {
// Checks if there is a mismatch between the plugin compileSdkVersion and the project compileSdkVersion.
if (pluginProject.android.compileSdkVersion > project.android.compileSdkVersion) {
project.logger.quiet("Warning: The plugin ${pluginName} requires Android SDK version ${pluginProject.android.compileSdkVersion.substring(8)}.")
project.logger.quiet("Warning: The plugin ${pluginName} requires Android SDK version ${getCompileSdkFromProject(pluginProject)}.")
project.logger.quiet("For more information about build configuration, see $kWebsiteDeploymentAndroidBuildConfig.")
}

Expand Down Expand Up @@ -483,9 +483,11 @@ class FlutterPlugin implements Plugin<Project> {
/** Prints error message and fix for any plugin compileSdkVersion or ndkVersion that are higher than the project. */
private void detectLowCompileSdkVersionOrNdkVersion() {
project.afterEvaluate {
int projectCompileSdkVersion = Integer.MAX_VALUE // Default to int max if using a preview version to skip the sdk check.
if (project.android.compileSdkVersion.substring(8).isInteger()) { // Stable versions use ints, legacy preview uses string.
projectCompileSdkVersion = project.android.compileSdkVersion.substring(8) as int
// Default to int max if using a preview version to skip the sdk check.
int projectCompileSdkVersion = Integer.MAX_VALUE
// Stable versions use ints, legacy preview uses string.
if (getCompileSdkFromProject(project).isInteger()) {
projectCompileSdkVersion = getCompileSdkFromProject(project) as int
}
int maxPluginCompileSdkVersion = projectCompileSdkVersion
String ndkVersionIfUnspecified = "21.1.6352462" /* The default for AGP 4.1.0 used in old templates. */
Expand All @@ -496,7 +498,12 @@ class FlutterPlugin implements Plugin<Project> {
getPluginList().each { plugin ->
Project pluginProject = project.rootProject.findProject(plugin.key)
pluginProject.afterEvaluate {
int pluginCompileSdkVersion = pluginProject.android.compileSdkVersion.substring(8) as int
// Default to int min if using a preview version to skip the sdk check.
int pluginCompileSdkVersion = Integer.MIN_VALUE;
// Stable versions use ints, legacy preview uses string.
if (getCompileSdkFromProject(pluginProject).isInteger()) {
pluginCompileSdkVersion = getCompileSdkFromProject(pluginProject) as int;
}
maxPluginCompileSdkVersion = Math.max(pluginCompileSdkVersion, maxPluginCompileSdkVersion)
String pluginNdkVersion = pluginProject.android.ndkVersion ?: ndkVersionIfUnspecified
maxPluginNdkVersion = mostRecentSemanticVersion(pluginNdkVersion, maxPluginNdkVersion)
Expand All @@ -515,6 +522,14 @@ class FlutterPlugin implements Plugin<Project> {
}
}

/**
* Returns the portion of the compileSdkVersion string that corresponds to either the numeric
* or string version.
*/
private String getCompileSdkFromProject(Project gradleProject) {
return gradleProject.android.compileSdkVersion.substring(8);
}

/**
* Returns `true` if the given path contains an `android/build.gradle` file.
*/
Expand Down
@@ -0,0 +1,5 @@
# Android preview integration tests

This directory contains integration tests which would otherwise live in `integration.shard`,
but require a dependency on a CIPD-hosted preview version of Android (and therefore their own
test shard). For additional information see the README in the `../integration.shard` directory.
@@ -0,0 +1,135 @@
// 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:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';

import '../integration.shard/test_utils.dart';
import '../src/common.dart';

void main() {
late Directory tempDir;
late String flutterBin;
late Directory exampleAppDir;
late Directory pluginDir;
final RegExp compileSdkVersionMatch = RegExp(r'compileSdk [\w.]+');
final String builtApkPath = <String>['build', 'app', 'outputs', 'flutter-apk', 'app-debug.apk']
.join(platform.pathSeparator);

setUp(() async {
tempDir = createResolvedTempDirectorySync('flutter_plugin_test.');
flutterBin = fileSystem.path.join(
getFlutterRoot(),
'bin',
'flutter',
);
pluginDir = tempDir.childDirectory('aaa');
exampleAppDir = pluginDir.childDirectory('example');

processManager.runSync(<String>[
flutterBin,
...getLocalEngineArguments(),
'create',
'--template=plugin',
'--platforms=android',
'aaa',
], workingDirectory: tempDir.path);
});

tearDown(() async {
tryToDelete(tempDir);
});

test(
'build succeeds targeting string compileSdkVersion',
() async {
final File buildGradleFile = exampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle');
// write a build.gradle with compileSdkVersion as `android-UpsideDownCake` which is a string preview version
buildGradleFile.writeAsStringSync(
buildGradleFile.readAsStringSync().replaceFirst(compileSdkVersionMatch, 'compileSdkVersion "android-UpsideDownCake"'),
flush: true
);
expect(buildGradleFile.readAsStringSync(), contains('compileSdkVersion "android-UpsideDownCake"'));

final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'apk',
'--debug',
], workingDirectory: exampleAppDir.path);
expect(exampleAppDir.childDirectory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('apk')
.childDirectory('debug')
.childFile('app-debug.apk').existsSync(), true);
expect(result.stdout, contains('Built $builtApkPath'));
},
);

test(
'build succeeds targeting string compileSdkPreview',
() async {
final File buildGradleFile = exampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle');
// write a build.gradle with compileSdkPreview as `UpsideDownCake` which is a string preview version
buildGradleFile.writeAsStringSync(
buildGradleFile.readAsStringSync().replaceFirst(compileSdkVersionMatch, 'compileSdkPreview "UpsideDownCake"'),
flush: true
);
expect(buildGradleFile.readAsStringSync(), contains('compileSdkPreview "UpsideDownCake"'));

final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'apk',
'--debug',
], workingDirectory: exampleAppDir.path);
expect(exampleAppDir.childDirectory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('apk')
.childDirectory('debug')
.childFile('app-debug.apk').existsSync(), true);
expect(result.stdout, contains('Built $builtApkPath'));
},
);

test(
'build succeeds when both example app and plugin target compileSdkPreview',
() async {
final File appBuildGradleFile = exampleAppDir.childDirectory('android').childDirectory('app').childFile('build.gradle');
// write a build.gradle with compileSdkPreview as `UpsideDownCake` which is a string preview version
appBuildGradleFile.writeAsStringSync(
appBuildGradleFile.readAsStringSync().replaceFirst(compileSdkVersionMatch, 'compileSdkPreview "UpsideDownCake"'),
flush: true
);
expect(appBuildGradleFile.readAsStringSync(), contains('compileSdkPreview "UpsideDownCake"'));

final File pluginBuildGradleFile = pluginDir.childDirectory('android').childFile('build.gradle');
// change the plugin build.gradle to use a preview compile sdk version
pluginBuildGradleFile.writeAsStringSync(
pluginBuildGradleFile.readAsStringSync().replaceFirst(compileSdkVersionMatch, 'compileSdkPreview "UpsideDownCake"'),
flush: true
);
expect(pluginBuildGradleFile.readAsStringSync(), contains('compileSdkPreview "UpsideDownCake"'));

final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'apk',
'--debug',
], workingDirectory: exampleAppDir.path);
expect(exampleAppDir.childDirectory('build')
.childDirectory('app')
.childDirectory('outputs')
.childDirectory('apk')
.childDirectory('debug')
.childFile('app-debug.apk').existsSync(), true);
expect(result.stdout, contains('Built $builtApkPath'));
},
);
}
4 changes: 4 additions & 0 deletions packages/flutter_tools/test/integration.shard/README.md
Expand Up @@ -20,3 +20,7 @@ information for the `flutter` tool (since they are black-box tests that
run the tool as a subprocess, rather than being unit tests). For this
reason, they are in a separate shard when running on continuous
integration and are not run when calculating coverage.

## Adding new test files

When adding a new test file make sure that it ends with `_test.dart`, or else it will not be run.

This file was deleted.

0 comments on commit 9a72e1c

Please sign in to comment.