Skip to content

Commit

Permalink
[tool] Add option for Android compile SDK version to update-dependenc…
Browse files Browse the repository at this point in the history
…ies command (flutter#5010)

Adds option to `update-dependencies` command to update the compile SDK version of plugins or their example apps.
  • Loading branch information
camsim99 authored and PROGrand committed Nov 1, 2023
1 parent 4c1fda8 commit 21ae908
Show file tree
Hide file tree
Showing 4 changed files with 480 additions and 88 deletions.
12 changes: 12 additions & 0 deletions script/tool/lib/src/common/repository_package.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ class RepositoryPackage {
!isPlatformInterface &&
directory.basename != directory.parent.basename;

/// True if this appears to be an example package, according to package
/// conventions.
bool get isExample {
final RepositoryPackage? enclosingPackage = getEnclosingPackage();
if (enclosingPackage == null) {
// An example package is enclosed in another package.
return false;
}
// Check whether this is one of the enclosing package's examples.
return enclosingPackage.getExamples().any((RepositoryPackage p) => p.path == path);
}

/// Returns the Flutter example packages contained in the package, if any.
Iterable<RepositoryPackage> getExamples() {
final Directory exampleDirectory = directory.childDirectory('example');
Expand Down
176 changes: 127 additions & 49 deletions script/tool/lib/src/update_dependency_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,17 @@ class UpdateDependencyCommand extends PackageLoopingCommand {
argParser.addOption(_androidDependency,
help: 'An Android dependency to update.',
allowed: <String>[
'gradle',
_AndroidDepdencyType.gradle,
_AndroidDepdencyType.compileSdk,
_AndroidDepdencyType.compileSdkForExamples,
],
allowedHelp: <String, String>{
'gradle': 'Updates Gradle version used in plugin example apps.',
_AndroidDepdencyType.gradle:
'Updates Gradle version used in plugin example apps.',
_AndroidDepdencyType.compileSdk:
'Updates compileSdk version used to compile plugins.',
_AndroidDepdencyType.compileSdkForExamples:
'Updates compileSdk version used to compile plugin examples.',
});
argParser.addOption(
_versionFlag,
Expand Down Expand Up @@ -130,7 +137,7 @@ ${response.httpResponse.body}
if (version == null) {
printError('A version must be provided to update this dependency.');
throw ToolExit(_exitNoTargetVersion);
} else if (_targetAndroidDependency == 'gradle') {
} else if (_targetAndroidDependency == _AndroidDepdencyType.gradle) {
final RegExp validGradleVersionPattern = RegExp(r'^\d+(?:\.\d+){1,2}$');
final bool isValidGradleVersion =
validGradleVersionPattern.stringMatch(version) == version;
Expand All @@ -139,14 +146,24 @@ ${response.httpResponse.body}
'A version with a valid format (maximum 2-3 numbers separated by period) must be provided.');
throw ToolExit(_exitInvalidTargetVersion);
}
_targetVersion = version;
return;
} else if (_targetAndroidDependency == _AndroidDepdencyType.compileSdk ||
_targetAndroidDependency ==
_AndroidDepdencyType.compileSdkForExamples) {
final RegExp validSdkVersion = RegExp(r'^\d{1,2}$');
final bool isValidSdkVersion =
validSdkVersion.stringMatch(version) == version;
if (!isValidSdkVersion) {
printError(
'A valid Android SDK version number (1-2 digit numbers) must be provided.');
throw ToolExit(_exitInvalidTargetVersion);
}
} else {
// TODO(camsim99): Add other supported Android dependencies like the Android SDK and AGP.
// TODO(camsim99): Add other supported Android dependencies like the min/target Android SDK and AGP.
printError(
'Target Android dependency $_targetAndroidDependency is unrecognized.');
throw ToolExit(_exitIncorrectTargetDependency);
}
_targetVersion = version;
}
}

Expand Down Expand Up @@ -233,59 +250,114 @@ ${response.httpResponse.body}
/// an Android dependency.
Future<PackageResult> _runForAndroidDependency(
RepositoryPackage package) async {
if (_targetAndroidDependency == 'gradle') {
final Iterable<RepositoryPackage> packageExamples = package.getExamples();
bool updateRanForExamples = false;
for (final RepositoryPackage example in packageExamples) {
if (!example.platformDirectory(FlutterPlatform.android).existsSync()) {
continue;
}
if (_targetAndroidDependency == _AndroidDepdencyType.compileSdk) {
return _runForCompileSdkVersion(package);
} else if (_targetAndroidDependency == _AndroidDepdencyType.gradle ||
_targetAndroidDependency ==
_AndroidDepdencyType.compileSdkForExamples) {
return _runForAndroidDependencyOnExamples(package);
}

updateRanForExamples = true;
Directory gradleWrapperPropertiesDirectory =
example.platformDirectory(FlutterPlatform.android);
if (gradleWrapperPropertiesDirectory
return PackageResult.fail(<String>[
'Target Android dependency $_androidDependency is unrecognized.'
]);
}

Future<PackageResult> _runForAndroidDependencyOnExamples(
RepositoryPackage package) async {
final Iterable<RepositoryPackage> packageExamples = package.getExamples();
bool updateRanForExamples = false;
for (final RepositoryPackage example in packageExamples) {
if (!example.platformDirectory(FlutterPlatform.android).existsSync()) {
continue;
}

updateRanForExamples = true;
Directory androidDirectory =
example.platformDirectory(FlutterPlatform.android);
final File fileToUpdate;
final RegExp dependencyVersionPattern;
final String newDependencyVersionEntry;

if (_targetAndroidDependency == _AndroidDepdencyType.gradle) {
if (androidDirectory
.childDirectory('app')
.childDirectory('gradle')
.existsSync()) {
gradleWrapperPropertiesDirectory =
gradleWrapperPropertiesDirectory.childDirectory('app');
androidDirectory = androidDirectory.childDirectory('app');
}
final File gradleWrapperPropertiesFile =
gradleWrapperPropertiesDirectory
.childDirectory('gradle')
.childDirectory('wrapper')
.childFile('gradle-wrapper.properties');

final String gradleWrapperPropertiesContents =
gradleWrapperPropertiesFile.readAsStringSync();
final RegExp validGradleDistributionUrl =
fileToUpdate = androidDirectory
.childDirectory('gradle')
.childDirectory('wrapper')
.childFile('gradle-wrapper.properties');
dependencyVersionPattern =
RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true);
if (!validGradleDistributionUrl
.hasMatch(gradleWrapperPropertiesContents)) {
return PackageResult.fail(<String>[
'Unable to find a "distributionUrl" entry to update for ${package.displayName}.'
]);
}

print(
'${indentation}Updating ${getRelativePosixPath(example.directory, from: package.directory)} to "$_targetVersion"');
final String newGradleWrapperPropertiesContents =
gradleWrapperPropertiesContents.replaceFirst(
validGradleDistributionUrl,
'distributionUrl=https\\://services.gradle.org/distributions/gradle-$_targetVersion-all.zip');
// TODO(camsim99): Validate current AGP version against target Gradle
// version: https://github.com/flutter/flutter/issues/133887.
gradleWrapperPropertiesFile
.writeAsStringSync(newGradleWrapperPropertiesContents);
newDependencyVersionEntry =
'distributionUrl=https\\://services.gradle.org/distributions/gradle-$_targetVersion-all.zip';
} else if (_targetAndroidDependency ==
_AndroidDepdencyType.compileSdkForExamples) {
fileToUpdate =
androidDirectory.childDirectory('app').childFile('build.gradle');
dependencyVersionPattern = RegExp(
r'(compileSdk|compileSdkVersion) (\d{1,2}|flutter.compileSdkVersion)');
newDependencyVersionEntry = 'compileSdk $_targetVersion';
} else {
printError(
'Target Android dependency $_targetAndroidDependency is unrecognized.');
throw ToolExit(_exitIncorrectTargetDependency);
}

final String oldFileToUpdateContents = fileToUpdate.readAsStringSync();

if (!dependencyVersionPattern.hasMatch(oldFileToUpdateContents)) {
return PackageResult.fail(<String>[
'Unable to find a $_targetAndroidDependency version entry to update for ${example.displayName}.'
]);
}
return updateRanForExamples
? PackageResult.success()
: PackageResult.skip('No example apps run on Android.');

print(
'${indentation}Updating ${getRelativePosixPath(example.directory, from: package.directory)} to "$_targetVersion"');
final String newGradleWrapperPropertiesContents = oldFileToUpdateContents
.replaceFirst(dependencyVersionPattern, newDependencyVersionEntry);

fileToUpdate.writeAsStringSync(newGradleWrapperPropertiesContents);
}
return PackageResult.fail(<String>[
'Target Android dependency $_androidDependency is unrecognized.'
]);
return updateRanForExamples
? PackageResult.success()
: PackageResult.skip('No example apps run on Android.');
}

Future<PackageResult> _runForCompileSdkVersion(
RepositoryPackage package) async {
if (!package.platformDirectory(FlutterPlatform.android).existsSync()) {
return PackageResult.skip(
'Package ${package.displayName} does not run on Android.');
} else if (package.isExample) {
// We skip examples for this command.
return PackageResult.skip(
'Package ${package.displayName} is not a top-level package; run with "compileSdkForExamples" to update.');
}
final File buildConfigurationFile = package
.platformDirectory(FlutterPlatform.android)
.childFile('build.gradle');
final String buildConfigurationContents =
buildConfigurationFile.readAsStringSync();
final RegExp validCompileSdkVersion =
RegExp(r'(compileSdk|compileSdkVersion) \d{1,2}');

if (!validCompileSdkVersion.hasMatch(buildConfigurationContents)) {
return PackageResult.fail(<String>[
'Unable to find a compileSdk version entry to update for ${package.displayName}.'
]);
}
print('${indentation}Updating ${package.directory} to "$_targetVersion"');
final String newBuildConfigurationContents = buildConfigurationContents
.replaceFirst(validCompileSdkVersion, 'compileSdk $_targetVersion');
buildConfigurationFile.writeAsStringSync(newBuildConfigurationContents);

return PackageResult.success();
}

/// Returns information about the current dependency of [package] on
Expand Down Expand Up @@ -414,3 +486,9 @@ class _PubDependencyInfo {
}

enum _PubDependencyType { normal, dev }

class _AndroidDepdencyType {
static const String gradle = 'gradle';
static const String compileSdk = 'compileSdk';
static const String compileSdkForExamples = 'compileSdkForExamples';
}
10 changes: 10 additions & 0 deletions script/tool/test/common/repository_package_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ void main() {
final List<RepositoryPackage> examples = plugin.getExamples().toList();

expect(examples.length, 1);
expect(examples[0].isExample, isTrue);
expect(examples[0].path, getExampleDir(plugin).path);
});

Expand All @@ -112,6 +113,8 @@ void main() {
final List<RepositoryPackage> examples = plugin.getExamples().toList();

expect(examples.length, 2);
expect(examples[0].isExample, isTrue);
expect(examples[1].isExample, isTrue);
expect(examples[0].path,
getExampleDir(plugin).childDirectory('example1').path);
expect(examples[1].path,
Expand All @@ -125,6 +128,7 @@ void main() {
final List<RepositoryPackage> examples = package.getExamples().toList();

expect(examples.length, 1);
expect(examples[0].isExample, isTrue);
expect(examples[0].path, getExampleDir(package).path);
});

Expand All @@ -136,6 +140,8 @@ void main() {
final List<RepositoryPackage> examples = package.getExamples().toList();

expect(examples.length, 2);
expect(examples[0].isExample, isTrue);
expect(examples[1].isExample, isTrue);
expect(examples[0].path,
getExampleDir(package).childDirectory('example1').path);
expect(examples[1].path,
Expand All @@ -151,6 +157,7 @@ void main() {
expect(plugin.isAppFacing, false);
expect(plugin.isPlatformInterface, false);
expect(plugin.isFederated, false);
expect(plugin.isExample, isFalse);
});

test('handle app-facing packages', () {
Expand All @@ -160,6 +167,7 @@ void main() {
expect(plugin.isAppFacing, true);
expect(plugin.isPlatformInterface, false);
expect(plugin.isPlatformImplementation, false);
expect(plugin.isExample, isFalse);
});

test('handle platform interface packages', () {
Expand All @@ -170,6 +178,7 @@ void main() {
expect(plugin.isAppFacing, false);
expect(plugin.isPlatformInterface, true);
expect(plugin.isPlatformImplementation, false);
expect(plugin.isExample, isFalse);
});

test('handle platform implementation packages', () {
Expand All @@ -181,6 +190,7 @@ void main() {
expect(plugin.isAppFacing, false);
expect(plugin.isPlatformInterface, false);
expect(plugin.isPlatformImplementation, true);
expect(plugin.isExample, isFalse);
});
});

Expand Down
Loading

0 comments on commit 21ae908

Please sign in to comment.