Reland "Allow for gradle downloading missing SDK assets" (#28097) (#28355)
* Allow for gradle downloading missing SDK assets if SDK licenses are present.
* Improvements for windows testing
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 73aa44b..45e46bc 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -372,7 +372,7 @@
if (buildInfo.targetPlatform == null && devicePlatform == TargetPlatform.android_arm64)
buildInfo = buildInfo.withTargetPlatform(TargetPlatform.android_arm64);
- if (!prebuiltApplication) {
+ if (!prebuiltApplication || androidSdk.licensesAvailable && androidSdk.latestVersion == null) {
printTrace('Building APK');
final FlutterProject project = await FlutterProject.current();
await buildApk(
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
index 0bb5823..73432f0 100644
--- a/packages/flutter_tools/lib/src/android/android_sdk.dart
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -263,7 +263,7 @@
class AndroidSdk {
AndroidSdk(this.directory, [this.ndk]) {
- _init();
+ reinitialize();
}
static const String _javaHomeEnvironmentVariable = 'JAVA_HOME';
@@ -278,6 +278,23 @@
List<AndroidSdkVersion> _sdkVersions;
AndroidSdkVersion _latestVersion;
+ /// Whether the `platform-tools` directory exists in the Android SDK.
+ ///
+ /// It is possible to have an Android SDK folder that is missing this with
+ /// the expectation that it will be downloaded later, e.g. by gradle or the
+ /// sdkmanager. The [licensesAvailable] property should be used to determine
+ /// whether the licenses are at least possibly accepted.
+ bool get platformToolsAvailable => fs.directory(fs.path.join(directory, 'platform-tools')).existsSync();
+
+ /// Whether the `licenses` directory exists in the Android SDK.
+ ///
+ /// The existence of this folder normally indicates that the SDK licenses have
+ /// been accepted, e.g. via the sdkmanager, Android Studio, or by copying them
+ /// from another workstation such as in CI scenarios. If these files are valid
+ /// gradle or the sdkmanager will be able to download and use other parts of
+ /// the SDK on demand.
+ bool get licensesAvailable => fs.directory(fs.path.join(directory, 'licenses')).existsSync();
+
static AndroidSdk locateAndroidSdk() {
String findAndroidHomeDir() {
String androidHomeDir;
@@ -348,14 +365,14 @@
}
static bool validSdkDirectory(String dir) {
- return fs.isDirectorySync(fs.path.join(dir, 'platform-tools'));
+ return fs.isDirectorySync(fs.path.join(dir, 'licenses'));
}
List<AndroidSdkVersion> get sdkVersions => _sdkVersions;
AndroidSdkVersion get latestVersion => _latestVersion;
- String get adbPath => getPlatformToolsPath('adb');
+ String get adbPath => getPlatformToolsPath(platform.isWindows ? 'adb.exe' : 'adb');
String get emulatorPath => getEmulatorPath();
@@ -376,8 +393,8 @@
/// Validate the Android SDK. This returns an empty list if there are no
/// issues; otherwise, it returns a list of issues found.
List<String> validateSdkWellFormed() {
- if (!processManager.canRun(adbPath))
- return <String>['Android SDK file not found: $adbPath.'];
+ if (adbPath == null || !processManager.canRun(adbPath))
+ return <String>['Android SDK file not found: ${adbPath ?? 'adb'}.'];
if (sdkVersions.isEmpty || latestVersion == null) {
final StringBuffer msg = StringBuffer('No valid Android SDK platforms found in ${_platformsDir.path}.');
@@ -396,7 +413,10 @@
}
String getPlatformToolsPath(String binaryName) {
- return fs.path.join(directory, 'platform-tools', binaryName);
+ final String path = fs.path.join(directory, 'platform-tools', binaryName);
+ if (fs.file(path).existsSync())
+ return path;
+ return null;
}
String getEmulatorPath() {
@@ -420,7 +440,11 @@
return null;
}
- void _init() {
+ /// Sets up various paths used internally.
+ ///
+ /// This method should be called in a case where the tooling may have updated
+ /// SDK artifacts, such as after running a gradle build.
+ void reinitialize() {
List<Version> buildTools = <Version>[]; // 19.1.0, 22.0.1, ...
final Directory buildToolsDir = fs.directory(fs.path.join(directory, 'build-tools'));
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index d7376fb..5da192c 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -105,6 +105,11 @@
return ValidationResult(ValidationType.missing, messages);
}
+ if (androidSdk.licensesAvailable && !androidSdk.platformToolsAvailable) {
+ messages.add(ValidationMessage.hint(userMessages.androidSdkLicenseOnly(kAndroidHome)));
+ return ValidationResult(ValidationType.partial, messages);
+ }
+
messages.add(ValidationMessage(userMessages.androidSdkLocation(androidSdk.directory)));
messages.add(ValidationMessage(androidSdk.ndk == null
@@ -249,7 +254,9 @@
}
}
- _ensureCanRunSdkManager();
+ if (!_canRunSdkManager()) {
+ return LicensesAccepted.unknown;
+ }
final Process process = await runCommand(
<String>[androidSdk.sdkManagerPath, '--licenses'],
@@ -279,7 +286,9 @@
return false;
}
- _ensureCanRunSdkManager();
+ if (!_canRunSdkManager()) {
+ throwToolExit(userMessages.androidMissingSdkManager(androidSdk.sdkManagerPath));
+ }
final Version sdkManagerVersion = Version.parse(androidSdk.sdkManagerVersion);
if (sdkManagerVersion == null || sdkManagerVersion.major < 26) {
@@ -306,10 +315,9 @@
return exitCode == 0;
}
- static void _ensureCanRunSdkManager() {
+ static bool _canRunSdkManager() {
assert(androidSdk != null);
final String sdkManagerPath = androidSdk.sdkManagerPath;
- if (!processManager.canRun(sdkManagerPath))
- throwToolExit(userMessages.androidMissingSdkManager(sdkManagerPath));
+ return processManager.canRun(sdkManagerPath);
}
}
diff --git a/packages/flutter_tools/lib/src/android/apk.dart b/packages/flutter_tools/lib/src/android/apk.dart
index 46de467..4a0ded0 100644
--- a/packages/flutter_tools/lib/src/android/apk.dart
+++ b/packages/flutter_tools/lib/src/android/apk.dart
@@ -8,7 +8,6 @@
import '../base/common.dart';
import '../build_info.dart';
-import '../globals.dart';
import '../project.dart';
import 'android_sdk.dart';
@@ -32,18 +31,11 @@
if (androidSdk == null)
throwToolExit('No Android SDK found. Try setting the ANDROID_SDK_ROOT environment variable.');
- final List<String> validationResult = androidSdk.validateSdkWellFormed();
- if (validationResult.isNotEmpty) {
- for (String message in validationResult) {
- printError(message, wrap: false);
- }
- throwToolExit('Try re-installing or updating your Android SDK.');
- }
-
- return buildGradleProject(
+ await buildGradleProject(
project: project,
buildInfo: buildInfo,
target: target,
isBuildingBundle: false
);
+ androidSdk.reinitialize();
}
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index a51f6eb..60fb758 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -112,6 +112,21 @@
return _cachedGradleProject;
}
+/// Runs `gradlew dependencies`, ensuring that dependencies are resolved and
+/// potentially downloaded.
+Future<void> checkGradleDependencies() async {
+ final Status progress = logger.startProgress('Ensuring gradle dependencies are up to date...', timeout: kSlowOperation);
+ final FlutterProject flutterProject = await FlutterProject.current();
+ final String gradle = await _ensureGradle(flutterProject);
+ await runCheckedAsync(
+ <String>[gradle, 'dependencies'],
+ workingDirectory: flutterProject.android.hostAppGradleRoot.path,
+ environment: _gradleEnv,
+ );
+ androidSdk.reinitialize();
+ progress.stop();
+}
+
// Note: Dependencies are resolved and possibly downloaded as a side-effect
// of calculating the app properties using Gradle. This may take minutes.
Future<GradleProject> _readGradleProject() async {
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index e274519..3822ea2 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -34,6 +34,9 @@
case TargetPlatform.android_arm64:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
+ if (androidSdk?.licensesAvailable == true && androidSdk.latestVersion == null) {
+ await checkGradleDependencies();
+ }
return applicationBinary == null
? await AndroidApk.fromAndroidProject((await FlutterProject.current()).android)
: AndroidApk.fromApk(applicationBinary);
diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart
index f7ceef6..230e7e4 100644
--- a/packages/flutter_tools/lib/src/base/user_messages.dart
+++ b/packages/flutter_tools/lib/src/base/user_messages.dart
@@ -45,6 +45,15 @@
String androidCantRunJavaBinary(String javaBinary) => 'Cannot execute $javaBinary to determine the version';
String get androidUnknownJavaVersion => 'Could not determine java version';
String androidJavaVersion(String javaVersion) => 'Java version $javaVersion';
+ String androidSdkLicenseOnly(String envKey) =>
+ 'Android SDK contains licenses only.\n'
+ 'Your first build of an Android application will take longer than usual, '
+ 'while gradle downloads the missing components. This functionality will '
+ 'only work if the licenses in the licenses folder in $envKey are valid.\n'
+ 'If the Android SDK has been installed to another location, set $envKey to that location.\n'
+ 'You may also want to add it to your PATH environment variable.\n\n'
+ 'Certain features, such as `flutter emulators` and `flutter devices`, will '
+ 'not work without the currently missing SDK components.';
String androidBadSdkDir(String envKey, String homeDir) =>
'$envKey = $homeDir\n'
'but Android SDK not found at this location.';
@@ -53,7 +62,7 @@
'Install Android Studio from: https://developer.android.com/studio/index.html\n'
'On first launch it will assist you in installing the Android SDK components.\n'
'(or visit https://flutter.io/setup/#android-setup for detailed instructions).\n'
- 'If Android SDK has been installed to a custom location, set $envKey to that location.\n'
+ 'If the Android SDK has been installed to a custom location, set $envKey to that location.\n'
'You may also want to add it to your PATH environment variable.\n';
String androidSdkLocation(String directory) => 'Android SDK at $directory';
String androidSdkPlatformToolsVersion(String platform, String tools) =>
@@ -75,7 +84,11 @@
String get androidLicensesAll => 'All Android licenses accepted.';
String get androidLicensesSome => 'Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses';
String get androidLicensesNone => 'Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses';
- String get androidLicensesUnknown => 'Android license status unknown.';
+ String get androidLicensesUnknown =>
+ 'Android license status unknown.\n'
+ 'Try re-installing or updating your Android SDK Manager.\n'
+ 'See https://developer.android.com/studio/#downloads or visit '
+ 'https://flutter.io/setup/#android-setup for detailed instructions.';
String androidSdkManagerOutdated(String managerPath) =>
'A newer version of the Android SDK is required. To update, run:\n'
'$managerPath --update\n';