Skip to content

Commit 64dbd3a

Browse files
authored
[tool] Add option for Android compile SDK version to update-dependencies command (flutter#5010)
Adds option to `update-dependencies` command to update the compile SDK version of plugins or their example apps.
1 parent 1ac75cf commit 64dbd3a

File tree

4 files changed

+480
-88
lines changed

4 files changed

+480
-88
lines changed

script/tool/lib/src/common/repository_package.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ class RepositoryPackage {
142142
!isPlatformInterface &&
143143
directory.basename != directory.parent.basename;
144144

145+
/// True if this appears to be an example package, according to package
146+
/// conventions.
147+
bool get isExample {
148+
final RepositoryPackage? enclosingPackage = getEnclosingPackage();
149+
if (enclosingPackage == null) {
150+
// An example package is enclosed in another package.
151+
return false;
152+
}
153+
// Check whether this is one of the enclosing package's examples.
154+
return enclosingPackage.getExamples().any((RepositoryPackage p) => p.path == path);
155+
}
156+
145157
/// Returns the Flutter example packages contained in the package, if any.
146158
Iterable<RepositoryPackage> getExamples() {
147159
final Directory exampleDirectory = directory.childDirectory('example');

script/tool/lib/src/update_dependency_command.dart

Lines changed: 127 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,17 @@ class UpdateDependencyCommand extends PackageLoopingCommand {
4242
argParser.addOption(_androidDependency,
4343
help: 'An Android dependency to update.',
4444
allowed: <String>[
45-
'gradle',
45+
_AndroidDepdencyType.gradle,
46+
_AndroidDepdencyType.compileSdk,
47+
_AndroidDepdencyType.compileSdkForExamples,
4648
],
4749
allowedHelp: <String, String>{
48-
'gradle': 'Updates Gradle version used in plugin example apps.',
50+
_AndroidDepdencyType.gradle:
51+
'Updates Gradle version used in plugin example apps.',
52+
_AndroidDepdencyType.compileSdk:
53+
'Updates compileSdk version used to compile plugins.',
54+
_AndroidDepdencyType.compileSdkForExamples:
55+
'Updates compileSdk version used to compile plugin examples.',
4956
});
5057
argParser.addOption(
5158
_versionFlag,
@@ -130,7 +137,7 @@ ${response.httpResponse.body}
130137
if (version == null) {
131138
printError('A version must be provided to update this dependency.');
132139
throw ToolExit(_exitNoTargetVersion);
133-
} else if (_targetAndroidDependency == 'gradle') {
140+
} else if (_targetAndroidDependency == _AndroidDepdencyType.gradle) {
134141
final RegExp validGradleVersionPattern = RegExp(r'^\d+(?:\.\d+){1,2}$');
135142
final bool isValidGradleVersion =
136143
validGradleVersionPattern.stringMatch(version) == version;
@@ -139,14 +146,24 @@ ${response.httpResponse.body}
139146
'A version with a valid format (maximum 2-3 numbers separated by period) must be provided.');
140147
throw ToolExit(_exitInvalidTargetVersion);
141148
}
142-
_targetVersion = version;
143-
return;
149+
} else if (_targetAndroidDependency == _AndroidDepdencyType.compileSdk ||
150+
_targetAndroidDependency ==
151+
_AndroidDepdencyType.compileSdkForExamples) {
152+
final RegExp validSdkVersion = RegExp(r'^\d{1,2}$');
153+
final bool isValidSdkVersion =
154+
validSdkVersion.stringMatch(version) == version;
155+
if (!isValidSdkVersion) {
156+
printError(
157+
'A valid Android SDK version number (1-2 digit numbers) must be provided.');
158+
throw ToolExit(_exitInvalidTargetVersion);
159+
}
144160
} else {
145-
// TODO(camsim99): Add other supported Android dependencies like the Android SDK and AGP.
161+
// TODO(camsim99): Add other supported Android dependencies like the min/target Android SDK and AGP.
146162
printError(
147163
'Target Android dependency $_targetAndroidDependency is unrecognized.');
148164
throw ToolExit(_exitIncorrectTargetDependency);
149165
}
166+
_targetVersion = version;
150167
}
151168
}
152169

@@ -233,59 +250,114 @@ ${response.httpResponse.body}
233250
/// an Android dependency.
234251
Future<PackageResult> _runForAndroidDependency(
235252
RepositoryPackage package) async {
236-
if (_targetAndroidDependency == 'gradle') {
237-
final Iterable<RepositoryPackage> packageExamples = package.getExamples();
238-
bool updateRanForExamples = false;
239-
for (final RepositoryPackage example in packageExamples) {
240-
if (!example.platformDirectory(FlutterPlatform.android).existsSync()) {
241-
continue;
242-
}
253+
if (_targetAndroidDependency == _AndroidDepdencyType.compileSdk) {
254+
return _runForCompileSdkVersion(package);
255+
} else if (_targetAndroidDependency == _AndroidDepdencyType.gradle ||
256+
_targetAndroidDependency ==
257+
_AndroidDepdencyType.compileSdkForExamples) {
258+
return _runForAndroidDependencyOnExamples(package);
259+
}
243260

244-
updateRanForExamples = true;
245-
Directory gradleWrapperPropertiesDirectory =
246-
example.platformDirectory(FlutterPlatform.android);
247-
if (gradleWrapperPropertiesDirectory
261+
return PackageResult.fail(<String>[
262+
'Target Android dependency $_androidDependency is unrecognized.'
263+
]);
264+
}
265+
266+
Future<PackageResult> _runForAndroidDependencyOnExamples(
267+
RepositoryPackage package) async {
268+
final Iterable<RepositoryPackage> packageExamples = package.getExamples();
269+
bool updateRanForExamples = false;
270+
for (final RepositoryPackage example in packageExamples) {
271+
if (!example.platformDirectory(FlutterPlatform.android).existsSync()) {
272+
continue;
273+
}
274+
275+
updateRanForExamples = true;
276+
Directory androidDirectory =
277+
example.platformDirectory(FlutterPlatform.android);
278+
final File fileToUpdate;
279+
final RegExp dependencyVersionPattern;
280+
final String newDependencyVersionEntry;
281+
282+
if (_targetAndroidDependency == _AndroidDepdencyType.gradle) {
283+
if (androidDirectory
248284
.childDirectory('app')
249285
.childDirectory('gradle')
250286
.existsSync()) {
251-
gradleWrapperPropertiesDirectory =
252-
gradleWrapperPropertiesDirectory.childDirectory('app');
287+
androidDirectory = androidDirectory.childDirectory('app');
253288
}
254-
final File gradleWrapperPropertiesFile =
255-
gradleWrapperPropertiesDirectory
256-
.childDirectory('gradle')
257-
.childDirectory('wrapper')
258-
.childFile('gradle-wrapper.properties');
259-
260-
final String gradleWrapperPropertiesContents =
261-
gradleWrapperPropertiesFile.readAsStringSync();
262-
final RegExp validGradleDistributionUrl =
289+
fileToUpdate = androidDirectory
290+
.childDirectory('gradle')
291+
.childDirectory('wrapper')
292+
.childFile('gradle-wrapper.properties');
293+
dependencyVersionPattern =
263294
RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true);
264-
if (!validGradleDistributionUrl
265-
.hasMatch(gradleWrapperPropertiesContents)) {
266-
return PackageResult.fail(<String>[
267-
'Unable to find a "distributionUrl" entry to update for ${package.displayName}.'
268-
]);
269-
}
270-
271-
print(
272-
'${indentation}Updating ${getRelativePosixPath(example.directory, from: package.directory)} to "$_targetVersion"');
273-
final String newGradleWrapperPropertiesContents =
274-
gradleWrapperPropertiesContents.replaceFirst(
275-
validGradleDistributionUrl,
276-
'distributionUrl=https\\://services.gradle.org/distributions/gradle-$_targetVersion-all.zip');
277295
// TODO(camsim99): Validate current AGP version against target Gradle
278296
// version: https://github.com/flutter/flutter/issues/133887.
279-
gradleWrapperPropertiesFile
280-
.writeAsStringSync(newGradleWrapperPropertiesContents);
297+
newDependencyVersionEntry =
298+
'distributionUrl=https\\://services.gradle.org/distributions/gradle-$_targetVersion-all.zip';
299+
} else if (_targetAndroidDependency ==
300+
_AndroidDepdencyType.compileSdkForExamples) {
301+
fileToUpdate =
302+
androidDirectory.childDirectory('app').childFile('build.gradle');
303+
dependencyVersionPattern = RegExp(
304+
r'(compileSdk|compileSdkVersion) (\d{1,2}|flutter.compileSdkVersion)');
305+
newDependencyVersionEntry = 'compileSdk $_targetVersion';
306+
} else {
307+
printError(
308+
'Target Android dependency $_targetAndroidDependency is unrecognized.');
309+
throw ToolExit(_exitIncorrectTargetDependency);
310+
}
311+
312+
final String oldFileToUpdateContents = fileToUpdate.readAsStringSync();
313+
314+
if (!dependencyVersionPattern.hasMatch(oldFileToUpdateContents)) {
315+
return PackageResult.fail(<String>[
316+
'Unable to find a $_targetAndroidDependency version entry to update for ${example.displayName}.'
317+
]);
281318
}
282-
return updateRanForExamples
283-
? PackageResult.success()
284-
: PackageResult.skip('No example apps run on Android.');
319+
320+
print(
321+
'${indentation}Updating ${getRelativePosixPath(example.directory, from: package.directory)} to "$_targetVersion"');
322+
final String newGradleWrapperPropertiesContents = oldFileToUpdateContents
323+
.replaceFirst(dependencyVersionPattern, newDependencyVersionEntry);
324+
325+
fileToUpdate.writeAsStringSync(newGradleWrapperPropertiesContents);
285326
}
286-
return PackageResult.fail(<String>[
287-
'Target Android dependency $_androidDependency is unrecognized.'
288-
]);
327+
return updateRanForExamples
328+
? PackageResult.success()
329+
: PackageResult.skip('No example apps run on Android.');
330+
}
331+
332+
Future<PackageResult> _runForCompileSdkVersion(
333+
RepositoryPackage package) async {
334+
if (!package.platformDirectory(FlutterPlatform.android).existsSync()) {
335+
return PackageResult.skip(
336+
'Package ${package.displayName} does not run on Android.');
337+
} else if (package.isExample) {
338+
// We skip examples for this command.
339+
return PackageResult.skip(
340+
'Package ${package.displayName} is not a top-level package; run with "compileSdkForExamples" to update.');
341+
}
342+
final File buildConfigurationFile = package
343+
.platformDirectory(FlutterPlatform.android)
344+
.childFile('build.gradle');
345+
final String buildConfigurationContents =
346+
buildConfigurationFile.readAsStringSync();
347+
final RegExp validCompileSdkVersion =
348+
RegExp(r'(compileSdk|compileSdkVersion) \d{1,2}');
349+
350+
if (!validCompileSdkVersion.hasMatch(buildConfigurationContents)) {
351+
return PackageResult.fail(<String>[
352+
'Unable to find a compileSdk version entry to update for ${package.displayName}.'
353+
]);
354+
}
355+
print('${indentation}Updating ${package.directory} to "$_targetVersion"');
356+
final String newBuildConfigurationContents = buildConfigurationContents
357+
.replaceFirst(validCompileSdkVersion, 'compileSdk $_targetVersion');
358+
buildConfigurationFile.writeAsStringSync(newBuildConfigurationContents);
359+
360+
return PackageResult.success();
289361
}
290362

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

416488
enum _PubDependencyType { normal, dev }
489+
490+
class _AndroidDepdencyType {
491+
static const String gradle = 'gradle';
492+
static const String compileSdk = 'compileSdk';
493+
static const String compileSdkForExamples = 'compileSdkForExamples';
494+
}

script/tool/test/common/repository_package_test.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ void main() {
102102
final List<RepositoryPackage> examples = plugin.getExamples().toList();
103103

104104
expect(examples.length, 1);
105+
expect(examples[0].isExample, isTrue);
105106
expect(examples[0].path, getExampleDir(plugin).path);
106107
});
107108

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

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

127130
expect(examples.length, 1);
131+
expect(examples[0].isExample, isTrue);
128132
expect(examples[0].path, getExampleDir(package).path);
129133
});
130134

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

138142
expect(examples.length, 2);
143+
expect(examples[0].isExample, isTrue);
144+
expect(examples[1].isExample, isTrue);
139145
expect(examples[0].path,
140146
getExampleDir(package).childDirectory('example1').path);
141147
expect(examples[1].path,
@@ -151,6 +157,7 @@ void main() {
151157
expect(plugin.isAppFacing, false);
152158
expect(plugin.isPlatformInterface, false);
153159
expect(plugin.isFederated, false);
160+
expect(plugin.isExample, isFalse);
154161
});
155162

156163
test('handle app-facing packages', () {
@@ -160,6 +167,7 @@ void main() {
160167
expect(plugin.isAppFacing, true);
161168
expect(plugin.isPlatformInterface, false);
162169
expect(plugin.isPlatformImplementation, false);
170+
expect(plugin.isExample, isFalse);
163171
});
164172

165173
test('handle platform interface packages', () {
@@ -170,6 +178,7 @@ void main() {
170178
expect(plugin.isAppFacing, false);
171179
expect(plugin.isPlatformInterface, true);
172180
expect(plugin.isPlatformImplementation, false);
181+
expect(plugin.isExample, isFalse);
173182
});
174183

175184
test('handle platform implementation packages', () {
@@ -181,6 +190,7 @@ void main() {
181190
expect(plugin.isAppFacing, false);
182191
expect(plugin.isPlatformInterface, false);
183192
expect(plugin.isPlatformImplementation, true);
193+
expect(plugin.isExample, isFalse);
184194
});
185195
});
186196

0 commit comments

Comments
 (0)