Skip to content

Commit

Permalink
[ci] Add a legacy Android build-all test (flutter#4005)
Browse files Browse the repository at this point in the history
Adds the ability to replace portions of the `flutter create`d app with saved copies, and adds a second build-all phase for Android that uses a Flutter 2.0.6-created `android/` directory (AGP 4.1, Gradle 6.7) to catch issues like flutter/flutter#125621 and flutter/flutter#125482 prior to release.

Includes some incidental cleanup:
- Extracts a helper method for adjusting files, so that this doesn't add even more copies of basically identical code.
    - (This was motivated by an earlier version of the PR that added modifications to several more files, which I ended up undoing, but the cleanup seemed worth keeping.)
- Adds missing unit test coverage.
- Reworks the unit tests to use a mock process manager and dummy files, instead of each test actually calling `flutter create`, which made each new test add several seconds to the unit test suite.
    - While this reduces the integration-style coverage, in practice the integration tests of the repo tooling is the CI itself, so the unit tests should be true unit tests.
- Changes the non-legacy test to Kotlin; we were still testing with Java even though Kotlin has been the default for quite a while, so we weren't testing what most new users would actually be running. Since we now have a legacy test, I used Java there to cover both.
- Removes some dead code for modifying the AndroidManifest.xml; when trying to set up unit tests for it I discovered that it no longer matches anything in an actual project. It dates back to the original command, and seems to have been a camera-related hack, which we clearly no longer need since it wasn't working and camera still works in build-all.

This is captured somewhat in the README in the legacy project directory, but to document the approach here: originally I was going to add flags to change individual items (AGP version, Gradle version), but quickly ran into the fact that selective downgrading is extremely fragile. E.g.,:
- The Kotlin version set in current projects doesn't work when downgrading AGP and Gradle.
- The app template can unconditionally use anything (e.g., `namespace`) that the AGP version it uses supports, so arbitrary future breakage is possible.

It's also less useful as a real test of what a plugin client's project likely looks like. Starting with a complete platform directory, and doing whatever the minimal changes are to keep it working, will likely reflect a common real-world scenario. On the flip side, the reason this doesn't use a complete 2.0.6 project, but instead is based on specific platform directories, is that we don't want to waste time manually maintaining, e.g., old Dart code that is irrelevant to the goal of the test. For now this only uses Android because that's where we've seen problems in practice, but we can alway add other legacy platform tests later if we find a need.

Fixes flutter/flutter#125689
  • Loading branch information
stuartmorgan authored May 22, 2023
1 parent 44d1fcd commit 30ebcf3
Show file tree
Hide file tree
Showing 27 changed files with 885 additions and 162 deletions.
37 changes: 37 additions & 0 deletions .ci/legacy_project/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
This directory contains a partial snapshot of an old Flutter project; it is
intended to replace the corresponding parts of a newly Flutter-created project
to allow testing plugin builds with a legacy project.

It was originally created with Flutter 2.0.6. In general the guidelines are:
- Pieces here should be largely self-contained rather than portions of
major project components; for instance, it currently contains the entire
`android/` directory from a legacy project, rather than a subset of it
which would be combined with a subset of a new project's `android/`
directory. This is to avoid random breakage in the future due to
conflicts between those subsets. For instance, we could probably get
away with not including android/app/src/main/res for a while, and
instead layer in the versions from a new project, but then someday
if the resources were renamed, there would be dangling references to
the old resources in files that are included here.
- Updates over time should be minimal. We don't expect that an unchanged
project will keep working forever, but this directory should simulate
a developer who has done the bare minimum to keep their project working
as they have updated Flutter.
- Updates should be logged below.

The reason for the hybrid model, rather than checking in a full legacy
project, is to minimize unnecessary maintenance work. E.g., there's no
need to manually keep Dart code updated for Flutter changes just to
test legacy native Android build behaviors.

## Manual changes to files

The following are the changes relative to running:

```bash
flutter create -a java all_packages
```

and then deleting everything but `android/` from it:

- Added license boilerplate.
46 changes: 46 additions & 0 deletions .ci/legacy_project/all_packages/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Web related
lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
10 changes: 10 additions & 0 deletions .ci/legacy_project/all_packages/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: 1d9032c7e1d867f071f2277eb1673e8f9b0274e3
channel: unknown

project_type: app
11 changes: 11 additions & 0 deletions .ci/legacy_project/all_packages/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java

# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
47 changes: 47 additions & 0 deletions .ci/legacy_project/all_packages/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 30

defaultConfig {
applicationId "com.example.all_packages"
minSdkVersion 16
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}

buildTypes {
release {
signingConfig signingConfigs.debug
}
}
}

flutter {
source '../..'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.all_packages">
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.all_packages">
<application
android:label="all_packages"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2013 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.

package com.example.all_packages;

import io.flutter.embedding.android.FlutterActivity;

public class MainActivity extends FlutterActivity {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />

<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />

<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.all_packages">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
29 changes: 29 additions & 0 deletions .ci/legacy_project/all_packages/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
buildscript {
repositories {
google()
jcenter()
}

dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
}
}

allprojects {
repositories {
google()
jcenter()
}
}

rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
delete rootProject.buildDir
}
3 changes: 3 additions & 0 deletions .ci/legacy_project/all_packages/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
11 changes: 11 additions & 0 deletions .ci/legacy_project/all_packages/android/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
include ':app'

def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()

assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }

def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
5 changes: 5 additions & 0 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ task:
CHANNEL: "master"
CHANNEL: "stable"
<< : *BUILD_ALL_PACKAGES_APP_TEMPLATE
create_all_packages_app_legacy_script:
- $PLUGIN_TOOL_COMMAND create-all-packages-app --legacy-source=.ci/legacy_project/all_packages --output-dir=legacy/ --exclude script/configs/exclude_all_packages_app.yaml
build_all_packages_legacy_script:
- cd legacy/all_packages
- flutter build $BUILD_ALL_ARGS --debug
### Web tasks ###
- name: web-platform_tests
env:
Expand Down
15 changes: 13 additions & 2 deletions script/tool/lib/src/common/file_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,21 @@ import 'package:file/file.dart';
/// childFileWithSubcomponents(rootDir, ['foo', 'bar', 'baz.txt'])
/// creates a File representing /rootDir/foo/bar/baz.txt.
File childFileWithSubcomponents(Directory base, List<String> components) {
Directory dir = base;
final String basename = components.removeLast();
return childDirectoryWithSubcomponents(base, components).childFile(basename);
}

/// Returns a [Directory] created by appending everything in [components]
/// to [base] as subdirectories.
///
/// Example:
/// childFileWithSubcomponents(rootDir, ['foo', 'bar'])
/// creates a File representing /rootDir/foo/bar/.
Directory childDirectoryWithSubcomponents(
Directory base, List<String> components) {
Directory dir = base;
for (final String directoryName in components) {
dir = dir.childDirectory(directoryName);
}
return dir.childFile(basename);
return dir;
}
Loading

0 comments on commit 30ebcf3

Please sign in to comment.