gradle_utils.dart 9.12 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8 9 10
import 'package:meta/meta.dart';

import '../base/common.dart';
import '../base/file_system.dart';
11 12 13 14

import '../base/logger.dart';
import '../base/os.dart';
import '../base/platform.dart';
15 16 17 18
import '../base/utils.dart';
import '../base/version.dart';
import '../build_info.dart';
import '../cache.dart';
19
import '../globals_null_migrated.dart' as globals;
20 21 22
import '../project.dart';
import '../reporting/reporting.dart';

23
const String _defaultGradleVersion = '6.7';
24

25
final RegExp _androidPluginRegExp = RegExp(r'com\.android\.tools\.build:gradle:(\d+\.\d+\.\d+)');
26

27 28
/// Provides utilities to run a Gradle task, such as finding the Gradle executable
/// or constructing a Gradle project.
29
class GradleUtils {
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
  GradleUtils({
    @required Platform platform,
    @required Logger logger,
    @required FileSystem fileSystem,
    @required Cache cache,
    @required OperatingSystemUtils operatingSystemUtils,
  }) : _platform = platform,
       _logger = logger,
       _cache = cache,
       _fileSystem = fileSystem,
       _operatingSystemUtils = operatingSystemUtils;

  final Cache _cache;
  final FileSystem _fileSystem;
  final Platform _platform;
  final Logger _logger;
  final OperatingSystemUtils _operatingSystemUtils;

48 49 50 51
  /// Gets the Gradle executable path and prepares the Gradle project.
  /// This is the `gradlew` or `gradlew.bat` script in the `android/` directory.
  String getExecutable(FlutterProject project) {
    final Directory androidDir = project.android.hostAppGradleRoot;
52
    injectGradleWrapperIfNeeded(androidDir);
53 54

    final File gradle = androidDir.childFile(
55
      _platform.isWindows ? 'gradlew.bat' : 'gradlew',
56 57
    );
    if (gradle.existsSync()) {
58
      _logger.printTrace('Using gradle from ${gradle.absolute.path}.');
59 60
      // If the Gradle executable doesn't have execute permission,
      // then attempt to set it.
61
      _operatingSystemUtils.makeExecutable(gradle);
62 63 64 65 66 67 68 69
      return gradle.absolute.path;
    }
    throwToolExit(
      'Unable to locate gradlew script. Please check that ${gradle.path} '
      'exists or that ${gradle.dirname} can be read.'
    );
  }

70 71
  /// Injects the Gradle wrapper files if any of these files don't exist in [directory].
  void injectGradleWrapperIfNeeded(Directory directory) {
72 73
    copyDirectory(
      _cache.getArtifactDirectory('gradle_wrapper'),
74 75 76 77 78
      directory,
      shouldCopyFile: (File sourceFile, File destinationFile) {
        // Don't override the existing files in the project.
        return !destinationFile.existsSync();
      },
79 80 81
      onFileCopied: (File source, File dest) {
        _operatingSystemUtils.makeExecutable(dest);
      }
82 83
    );
    // Add the `gradle-wrapper.properties` file if it doesn't exist.
84 85 86 87 88 89 90 91 92 93 94
    final Directory propertiesDirectory = directory
      .childDirectory(_fileSystem.path.join('gradle', 'wrapper'));
    final File propertiesFile = propertiesDirectory
      .childFile('gradle-wrapper.properties');

    if (propertiesFile.existsSync()) {
      return;
    }
    propertiesDirectory.createSync(recursive: true);
    final String gradleVersion = getGradleVersionForAndroidPlugin(directory, _logger);
    final String propertyContents = '''
95 96 97 98 99
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersion-all.zip
100 101
''';
    propertiesFile.writeAsStringSync(propertyContents);
102 103 104 105 106 107 108 109
  }
}

/// Returns the Gradle version that the current Android plugin depends on when found,
/// otherwise it returns a default version.
///
/// The Android plugin version is specified in the [build.gradle] file within
/// the project's Android directory.
110
String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) {
111 112
  final File buildFile = directory.childFile('build.gradle');
  if (!buildFile.existsSync()) {
113
    logger.printTrace('$buildFile doesn\'t exist, assuming AGP version: $_defaultGradleVersion');
114 115 116 117 118
    return _defaultGradleVersion;
  }
  final String buildFileContent = buildFile.readAsStringSync();
  final Iterable<Match> pluginMatches = _androidPluginRegExp.allMatches(buildFileContent);
  if (pluginMatches.isEmpty) {
119
    logger.printTrace('$buildFile doesn\'t provide an AGP version, assuming AGP version: $_defaultGradleVersion');
120 121 122
    return _defaultGradleVersion;
  }
  final String androidPluginVersion = pluginMatches.first.group(1);
123
  logger.printTrace('$buildFile provides AGP version: $androidPluginVersion');
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
  return getGradleVersionFor(androidPluginVersion);
}

/// Returns true if [targetVersion] is within the range [min] and [max] inclusive.
bool _isWithinVersionRange(
  String targetVersion, {
  @required String min,
  @required String max,
}) {
  assert(min != null);
  assert(max != null);
  final Version parsedTargetVersion = Version.parse(targetVersion);
  return parsedTargetVersion >= Version.parse(min) &&
         parsedTargetVersion <= Version.parse(max);
}

/// Returns the Gradle version that is required by the given Android Gradle plugin version
/// by picking the largest compatible version from
/// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
143
@visibleForTesting
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
String getGradleVersionFor(String androidPluginVersion) {
  if (_isWithinVersionRange(androidPluginVersion, min: '1.0.0', max: '1.1.3')) {
    return '2.3';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '1.2.0', max: '1.3.1')) {
    return '2.9';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '1.5.0', max: '1.5.0')) {
    return '2.2.1';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '2.0.0', max: '2.1.2')) {
    return '2.13';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '2.1.3', max: '2.2.3')) {
    return '2.14.1';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '2.3.0', max: '2.9.9')) {
    return '3.3';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '3.0.0', max: '3.0.9')) {
    return '4.1';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '3.1.0', max: '3.1.9')) {
    return '4.4';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '3.2.0', max: '3.2.1')) {
    return '4.6';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '3.3.0', max: '3.3.2')) {
    return '4.10.2';
  }
  if (_isWithinVersionRange(androidPluginVersion, min: '3.4.0', max: '3.5.0')) {
    return '5.6.2';
  }
178 179 180
  if (_isWithinVersionRange(androidPluginVersion, min: '4.0.0', max: '4.1.0')) {
    return '6.7';
  }
181
  throwToolExit('Unsupported Android Plugin version: $androidPluginVersion.');
182 183 184 185 186 187 188 189 190 191 192 193
}

/// Overwrite local.properties in the specified Flutter project's Android
/// sub-project, if needed.
///
/// If [requireAndroidSdk] is true (the default) and no Android SDK is found,
/// this will fail with a [ToolExit].
void updateLocalProperties({
  @required FlutterProject project,
  BuildInfo buildInfo,
  bool requireAndroidSdk = true,
}) {
194
  if (requireAndroidSdk && globals.androidSdk == null) {
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
    exitWithNoSdkMessage();
  }
  final File localProperties = project.android.localPropertiesFile;
  bool changed = false;

  SettingsFile settings;
  if (localProperties.existsSync()) {
    settings = SettingsFile.parseFromFile(localProperties);
  } else {
    settings = SettingsFile();
    changed = true;
  }

  void changeIfNecessary(String key, String value) {
    if (settings.values[key] == value) {
      return;
    }
    if (value == null) {
      settings.values.remove(key);
    } else {
      settings.values[key] = value;
    }
    changed = true;
  }

220
  if (globals.androidSdk != null) {
221
    changeIfNecessary('sdk.dir', globals.fsUtils.escapePath(globals.androidSdk.directory.path));
222 223
  }

224
  changeIfNecessary('flutter.sdk', globals.fsUtils.escapePath(Cache.flutterRoot));
225 226 227 228 229
  if (buildInfo != null) {
    changeIfNecessary('flutter.buildMode', buildInfo.modeName);
    final String buildName = validatedBuildNameForPlatform(
      TargetPlatform.android_arm,
      buildInfo.buildName ?? project.manifest.buildName,
230
      globals.logger,
231 232 233 234 235
    );
    changeIfNecessary('flutter.versionName', buildName);
    final String buildNumber = validatedBuildNumberForPlatform(
      TargetPlatform.android_arm,
      buildInfo.buildNumber ?? project.manifest.buildNumber,
236
      globals.logger,
237 238 239 240 241 242 243 244 245 246 247 248 249 250
    );
    changeIfNecessary('flutter.versionCode', buildNumber?.toString());
  }

  if (changed) {
    settings.writeContents(localProperties);
  }
}

/// Writes standard Android local properties to the specified [properties] file.
///
/// Writes the path to the Android SDK, if known.
void writeLocalProperties(File properties) {
  final SettingsFile settings = SettingsFile();
251
  if (globals.androidSdk != null) {
252
    settings.values['sdk.dir'] = globals.fsUtils.escapePath(globals.androidSdk.directory.path);
253 254 255 256 257
  }
  settings.writeContents(properties);
}

void exitWithNoSdkMessage() {
258
  BuildEvent('unsupported-project', type: 'gradle', eventError: 'android-sdk-not-found', flutterUsage: globals.flutterUsage).send();
259
  throwToolExit(
260
    '${globals.logger.terminal.warningMark} No Android SDK found. '
261
    'Try setting the ANDROID_SDK_ROOT environment variable.'
262 263
  );
}